QT多线程
多线程
QThread类提供了多线程的支持,一般从QThread继承定义自己的线程类。Qt提供了QMutex、QWaitCondition、QSemaphore等同步机制,可以方便地实现线程间的同步。和 Qt Concurrent 模块,可以方便地实现多线程编程。
C++11标准线程
使用std::thread直接绑定函数或可调用对象。
#include <thread>
void task() { /* ... */ }
std::thread t(task);
t.join(); // 等待线程结束
生命周期管理:需手动调用join()(阻塞等待)或detach()(分离线程)
QT线程
方法一: 继承 QThread,重写 run()
-
本质:QThread 对象本身(即 this)生活在旧线程(通常是主线程),而重写的 run() 方法运行在新线程。
-
适用场景:一个独立的、长时间运行的“计算任务”或“工作线程”,与主线程交互较少。它更像是线程本身的控制器。
-
注意:在新线程的 run() 函数内创建的对象(局部变量)才属于新线程。
#include <QThread>
class MyThread : public QThread {
void run() override { /* ... */ }
};
MyThread thread;
thread.start();
方法二: 使用 moveToThread()
-
本质:将一个业务逻辑对象(Worker)的所有槽函数移动到另一个线程的事件循环中去执行。
QThread对象管理线程,Worker对象执行业务。 -
适用场景:需要利用 Qt 的事件循环机制(如计时器、网络套接字)或需要与主线程进行频繁、复杂的信号槽交互。这是 Qt 官方更推荐的现代风格。
-
核心:Worker 对象不能有父对象,否则无法
moveToThread。- 错误做法:Worker *worker = new Worker(this); (指定父对象)
- 正确做法:Worker *worker = new Worker; (无父对象)
-
自动连接 (Auto Connection):这是默认连接方式。当信号和槽在不同线程时,Qt 会自动将其转换为队列连接 (QueuedConnection)。这意味着信号发出后,事件会被放入接收者所在线程的事件循环中,等待被处理,从而实现线程安全的通信。
-
直接连接 (Direct Connection):槽函数在发送者的线程中立即执行,不是线程安全的。
// Worker.h
#include <QObject>
class Worker : public QObject
{
Q_OBJECT
public:
explicit Worker(QObject *parent = nullptr) : QObject(parent) {}
public slots:
void doWork(const QString ¶meter) {
// 在这里执行耗时的任务
QString result;
// ... 处理 parameter, 得到 result ...
emit workFinished(result); // 通过信号发送结果
}
signals:
void workFinished(const QString &result);
};
// MainWindow.cpp
QThread *workerThread = new QThread(this);
Worker *worker = new Worker; // **注意:不能指定父对象!**
worker->moveToThread(workerThread);
// 连接线程开始的信号到Worker的工作槽
connect(workerThread, &QThread::started, worker, [worker]() { worker->doWork("Start Data"); });
// 连接Worker完成工作的信号到主线程的UI更新槽(线程安全)
connect(worker, &Worker::workFinished, this, &MainWindow::handleResults);
// 连接线程结束的信号来销毁Worker对象
connect(workerThread, &QThread::finished, worker, &QObject::deleteLater);
workerThread->start();
核心的API介绍
构造函数
- QThread::QThread(QObject *parent = nullptr): 构造函数用于创建一个线程对象。可以指定父象。
常用方法
-
void QThread::start(QThread::Priority priority = InheritPriority): 启动线程。这个方法会创建一个新的线程,并在新线程中调用run()方法。priority参数用于设置线程的优先级。大多数情况下,使用默认的InheritPriority即可 -
QThread::quit()与QThread::exit()请求线程退出。这个方法会设置线程的退出标志,当线程的run()方法返回时,线程会自动退出。- 对于 moveToThread 方式:线程运行着一个由
exec()启动的事件循环。调用quit()是最正确、最安全的退出方式,它会优雅地退出事件循环,执行完当前正在处理的槽函数后,使run()方法返回。 - 对于重写 run() 的方式:如果你的 run() 函数是 while(condition) { ... } 而没有调用 exec(),那么 quit() 无效。你需要使用一个标志位来请求退出。
- 对于 moveToThread 方式:线程运行着一个由
class MyThread : public QThread {
Q_OBJECT
public:
void stop() { // 提供一个公共的停止接口
QMutexLocker locker(&m_mutex);
m_stopped = true;
}
protected:
void run() override {
// 初始化...
while (!isStopped()) { // 循环检查退出标志
// 执行工作任务...
// 可以偶尔检查一下,或者每次循环都检查
}
// 清理资源...
}
private:
bool isStopped() const {
QMutexLocker locker(&m_mutex);
return m_stopped;
}
mutable QMutex m_mutex;
bool m_stopped = false;
};
// 在主线程中调用
myThread->stop(); // 设置退出标志
myThread->wait(); // 等待线程真正退出
-
void QThread::terminate(): 强制终止线程。这个方法会立即停止线程,不保证线程资源的正确释放,因此不推荐使用,除非在紧急情况下。 -
void QThread::wait(unsigned long time = ULONG_MAX): 等待线程结束。参数 time 表示等待的最大时间(单位:毫秒)。如果 time 为 ULONG_MAX,则表示无限等待。- 优化建议:永远不要在当前线程内调用自身的
wait(),例如在 MyThread::run() 函数里调用 this->wait(),这将导致死锁。
- 优化建议:永远不要在当前线程内调用自身的
-
QThread::requestInterruption()和isInterruptionRequested():更优雅的线程中断机制,专门用于解决重写 run() 时线程退出的问题
class MyThread : public QThread {
protected:
void run() override {
while (!isInterruptionRequested()) { // 使用Qt内置的中断请求标志
// 执行工作任务...
}
// 清理资源...
}
};
// 在主线程中请求中断
myThread->requestInterruption();
myThread->quit(); // 如果线程在事件循环中,quit()也要调用
myThread->wait();
线程控制信号
- void QThread::started(): 当线程开始执行时发出此信号。
- void QThread::finished(): 当线程执行完毕时发出此信号。
- void QThread::terminated(): 当线程被强制终止时发出此信号。
线程状态
- bool QThread::isRunning() const: 检查线程是否正在运行。
- bool QThread::isFinished() const: 检查线程是否已经结束。
- bool QThread::isInterruptionRequested() const: 检查是否请求了线程中断。
线程优先级
- void QThread::setPriority(QThread::Priority priority) :设置线程的优先级。
- QThread::Priority QThread::priority() const: 获取线程的当前优先级。
事件循环
概念:所谓事件循环,就是循环执行消息响应,此时一旦消息队列有消息发生,就可以马上执行槽响应函数。
用途:Qt的事件循环Event Loop是线程内部的一个消息处理机制,用于管理事件(如信号槽调用、定时器、网络响应等)。当线程启动事件循环(通过 QThread::exec()),它会持续监听并分发事件,直到调用 QThread::quit() 或事件循环终止。
子类化 QThread,重写 run(),如果不手动调用exec(),线程不会进入事件循环,仅执行 run() 中的代码,完成后立即退出。
如果调用exec()进入事件循环:
// 方式一:直接创建并启动线程
class EventLoopThread : public QThread {
void run() override {
// 启动事件循环(持续监听事件)
exec();
}
};
EventLoopThread thread;
thread.start();
// 方式二:通过信号槽启动线程
// Worker 类(普通 QObject,不继承 QThread)
class Worker : public QObject {
Q_OBJECT
public slots:
void doWork() {
// 执行任务
emit workDone();
}
signals:
void workDone();
};
// 使用 moveToThread + 信号槽, 将 QObject 对象移动到新线程,通过信号槽触发任务。线程必须启动事件循环(默认调用 exec())才能处理信号槽。
// 在主线程中创建对象和线程
QThread *thread = new QThread;
Worker *worker = new Worker; // 在主线程创建
worker->moveToThread(thread); // 移动到子线程
// 通过信号触发任务
connect(thread, &QThread::started, worker, &Worker::doWork);
connect(worker, &Worker::workDone, thread, &QThread::quit); // 任务完成后退出事件循环
// 循环完成后释放资源
connect(thread, &QThread::finished, worker, &QObject::deleteLater);
connect(thread, &QThread::finished, thread, &QObject::deleteLater);
// 启动线程(自动调用 exec() 进入事件循环)
thread->start();
关键结论:
只有进入事件循环的线程,才能处理信号槽、定时器等事件。若线程需要响应外部请求(如通过信号槽),必须启动事件循环。优先使用 moveToThread,简化线程管理,避免手动处理事件循环。
事件循环与线程池的相似与区别:
- 事件循环:运行在单个线程中,通过一个任务队列(或事件队列)按顺序处理任务(如信号槽调用、定时器、I/O事件等), 线程持续检查队列中的事件,逐个执行,不创建新线程。
- 线程池: 维护一组固定或动态的线程,从共享任务队列中获取任务并并行执行。充分利用多核CPU资源,提高任务吞吐量。
| 特性 | 事件循环 | 线程池 |
|---|---|---|
| 线程数量 | 单线程 | 多线程 |
| 任务执行方式 | 顺序执行 | 并行执行 |
| 适用场景 | I/O密集型、事件驱动 | CPU密集型、高并发任务 |
| 资源消耗 | 低(单线程) | 较高(需管理多个线程) |
| 典型用途 | GUI事件处理、异步网络请求 | 图像渲染、科学计算、批量处理 |
#include <QCoreApplication>
#include <QThread>
#include <QTimer>
#include <QDebug>
#include <QTime>
// 工作类(处理任务)
class Worker : public QObject {
Q_OBJECT
public:
explicit Worker(QObject *parent = nullptr) : QObject(parent) {}
public slots:
// 任务1:定时心跳
void heartbeat() {
qDebug() << "[心跳] 线程ID:" << QThread::currentThreadId()
<< "时间:" << QTime::currentTime().toString("hh:mm:ss.zzz");
}
// 任务2:计算任务(外部触发)
void calculate() {
qDebug() << "[计算] 开始计算...线程ID:" << QThread::currentThreadId();
// 模拟耗时操作(但不阻塞事件循环)
QTime dieTime = QTime::currentTime().addSecs(2);
while (QTime::currentTime() < dieTime) {
// 处理事件,避免完全阻塞
QCoreApplication::processEvents();
QThread::msleep(100); // 小睡眠,减少CPU占用
}
qDebug() << "[计算] 计算完成";
emit calculationDone(); // 通知计算完成
}
// 任务3:即时任务
void immediateTask() {
qDebug() << "[即时任务] 立即执行,线程ID:" << QThread::currentThreadId();
}
signals:
void calculationDone();
};
int main(int argc, char *argv[]) {
QCoreApplication a(argc, argv);
qDebug() << "[主线程] ID:" << QThread::currentThreadId();
// 创建子线程并启动事件循环
QThread thread;
// 创建工作对象(堆上分配,由线程管理生命周期)
Worker *worker = new Worker();
worker->moveToThread(&thread);
// 连接计算完成信号到线程退出
QObject::connect(worker, &Worker::calculationDone, &thread, &QThread::quit);
// 连接线程结束信号来删除worker
QObject::connect(&thread, &QThread::finished, worker, &QObject::deleteLater);
// 启动线程
thread.start();
// 创建定时器(堆上分配,移动到子线程)
QTimer *timer = new QTimer();
timer->moveToThread(&thread);
timer->setInterval(1000); // 1秒间隔
// 连接定时器超时信号到worker的心跳槽
QObject::connect(timer, &QTimer::timeout, worker, &Worker::heartbeat);
// 使用QueuedConnection方式在子线程中启动定时器
QMetaObject::invokeMethod(timer, "start", Qt::QueuedConnection);
// 2. 外部触发任务:模拟点击按钮(在主线程触发)
QTimer::singleShot(3000, [worker] { // 3秒后触发
qDebug() << "\n[主线程] 发送计算任务到子线程";
QMetaObject::invokeMethod(worker, "calculate", Qt::QueuedConnection);
});
// 3. 添加一个即时任务到子线程队列
QMetaObject::invokeMethod(worker, "immediateTask", Qt::QueuedConnection);
// 10秒后退出程序
QTimer::singleShot(10000, &a, &QCoreApplication::quit);
// 连接应用程序退出信号到线程退出
QObject::connect(&a, &QCoreApplication::aboutToQuit, [&thread] {
if (thread.isRunning()) {
thread.quit();
thread.wait(1000); // 等待最多1秒
}
});
return a.exec();
}
- 使用
deleteLater()确保在线程安全的时候删除对象。 - 添加了
calculationDone信号,在工作完成后,通知线程退出。 - 使用
QMetaObject::invokeMethod跨线程触发任务。
跨线程任务调度
QMetaObject::invokeMethod
功能:通过方法名调用对象的成员函数(类似于直接调用 object.method(),但更灵活)。
核心用途:跨线程安全地触发任务,确保方法在目标线程的事件循环中执行。
源码:
bool QMetaObject::invokeMethod(
QObject *obj, // 目标对象
const char *methodName, // 方法名(字符串)
Qt::ConnectionType connectionType = Qt::AutoConnection, // 连接类型
QGenericReturnArgument returnValue = QGenericReturnArgument(), // 返回值
QGenericArgument val0 = QGenericArgument(), // 参数1
QGenericArgument val1 = QGenericArgument(), // 参数2
... // 更多参数(最多10个)
);
参数说明:
- obj(目标对象)
作用:指定要调用方法的对象。
要求:对象必须是 QObject 的派生类,且方法必须是 Q_INVOKABLE 宏标记的或信号/槽。 - methodName(方法名)
作用:要调用的方法名称,需使用字符串格式。
格式:可以是 methodName 或 SIGNAL(methodName)。
// 调用无参方法
QMetaObject::invokeMethod(obj, "doWork");
// 调用带参方法
QMetaObject::invokeMethod(obj, "setValue", Q_ARG(int, 42)); - connectionType(连接类型)
作用:决定方法调用的执行方式。
可选值:- Qt::DirectConnection:立即在调用线程执行(类似直接调用
obj->method())。 - Qt::QueuedConnection:将方法调用加入目标线程的事件队列,异步执行。
- Qt::BlockingQueuedConnection:同 QueuedConnection,但
阻塞调用线程直到方法执行完成。 - Qt::AutoConnection:自动选择(若对象在调用线程则 Direct,否则 Queued)。
- Qt::DirectConnection:立即在调用线程执行(类似直接调用
- returnValue(返回值)
作用:接收方法的返回值(如果有)。
要求:需使用 Q_RETURN_ARG 宏包装返回值类型和存储变量。
// 直接调用
QMetaObject::invokeMethod(worker, "immediateTask", Qt::DirectConnection);
// 队列调用(跨线程安全)
QMetaObject::invokeMethod(worker, "immediateTask", Qt::QueuedConnection);
// 调用带参数的方法
int result;
QMetaObject::invokeMethod(
obj,
"calculate",
Qt::DirectConnection,
Q_RETURN_ARG(int, result), // 返回值存入 result
Q_ARG(int, 10),
Q_ARG(int, 20)
);
qDebug() << "计算结果:" << result; // 输出 30
线程间通信
概念:线程间通信(Inter-Thread Communication,简称 ITC)是指两个或多个线程之间共享内存或消息传递的方式。
常见通信方式:
- 共享内存(Shared Memory):通过共享内存进行通信,需要考虑同步和同步问题。
- 消息传递(Message Passing):通过消息传递进行通信,不需要考虑同步问题,但需要考虑消息的传递顺序。
实现线程互斥和同步常用的类
- 互斥锁:QMutex、QMutexLocker
- 条件变量:QWaitCondition • 信号量:QSemaphore
- 读写锁:QReadLocker、QWriteLocker、QReadWriteLock
互斥锁
概念:互斥锁(Mutex)是一种用于保护共享资源的同步机制,它可以确保一次只有一个线程可以访问共享资源。当多个线程访问和修改共享资源时,如果不加以适当的控制,就可能产生竞态条件、数据不一致甚至程序崩溃等问题。
QMutex 类:是Qt框架提供的互斥锁类,用于保护共享资源的访问,实现线程间的互斥操作。在多线程环境下,通过互斥锁来控制对共享数据的访问,确保线程安全。
QMutex mutex;
mutex.lock(); //上锁
// 访问共享资源
mutex.unlock(); //解锁
QMutexLocker:QMutexLocker是QMutex的辅助类,使⽤RAII方式对互斥锁进行上锁和解锁操作。简化对互斥锁的上锁和解锁操作,避免忘记解锁导致的死锁等问题。
QMutex mutex; // 还是需要定义一个锁mutex。
{
QMutexLocker locker(&mutex); //在作⽤域内⾃动上锁
//访问共享资源
} //在作⽤域结束时⾃动解锁
QReadWriteLocker、QReadLocker、QWriteLocker:QReadWriteLocker、QReadLocker、QWriteLocker是QReadWriteLock的辅助类,用于读写锁的上锁和解锁操作。
QReadWriteLock:QReadWriteLock 是读写锁类,用于控制读和写的并发访问。QReadLocker:QReadLocker用于读操作上锁,允许多个线程同时读取共享资源。QWriteLocker:QWriteLocker 用于写操作上锁,只允许⼀个线程写入共享资源。
QReadWriteLock rwLock;
//在读操作中使⽤读锁
{
QReadLocker locker(&rwLock); //在作⽤域内⾃动上读锁
//读取共享资源
} //在作⽤域结束时⾃动解读锁
//在写操作中使⽤写锁
{
//修改共享资源
QWriteLocker locker(&rwLock); //在作⽤域内⾃动上写锁
} //在作⽤域结束时⾃动解写锁
使用:
#include <QThread>
#include <QMutex>
class myThread : public QThread
{
Q_OBJECT
public:
explicit myThread(QObject *parent = nullptr);
void run();
private:
static QMutex mutex; //多个线程使⽤⼀把锁
static int num; //多个线程访问⼀个数据
};
void myThread::run()
{
while(1)
{
this->mutex.lock(); //加锁
qDebug() << "Current Thread: " << this << ", Value: " << this->num++;
this->mutex.unlock(); //解锁
QThread::sleep(1); //线程睡眠两秒
}
}
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
myThread *t1 = new myThread(this);
myThread *t2 = new myThread(this);
t1->start();
t2->start();
}
// 执行结果:
// Current Thread: 0x10a000000, Value: 0
// Current Thread: 0x10a000000, Value: 1
// Current Thread: 0x10a000001, Value: 2
// Current Thread: 0x10a000001, Value: 3
//...
信号量
概念:信号量(Semaphore)是一种用于控制对共享资源的访问的同步机制,它允许多个线程同时访问共享资源,但同时只能允许一定数量的线程访问共享资源。在多线程环境中,多个线程可能需要访问有限的共享资源。如果没有适当的同步机制,就可能出现多个线程同时访问同一资源的情况,导致数据损坏或不一致。
QSemaphore类:是Qt框架提供的信号量类,用于控制对共享资源的访问,实现线程间的同步操作。限制并发线程数量,用于解决⼀些资源有限的问题。
acquire(int n):如果信号量的可用值 ≥ n,则减少 n 并继续执行;否则阻塞。release(int n):增加信号量的值 n,唤醒等待的线程available():返回当前信号量的值,用于调试或状态检查。
#include <QCoreApplication>
#include <QSemaphore>
#include <QThread>
#include <QDebug>
const int BufferSize = 5; // 缓冲区容量
QSemaphore freeSlots(BufferSize); // 空闲位置信号量(初始为5)
QSemaphore usedSlots(0); // 已用位置信号量(初始为0)
int buffer[BufferSize] = {0}; // 共享缓冲区
// 生产者线程
class Producer : public QThread {
protected:
void run() override {
for (int i = 0; i < 10; i++) { // 生产10个数据
// 等待空闲位置
freeSlots.acquire(1); // 获取1个空闲位置(若没有则阻塞)
// 生产数据并放入缓冲区
buffer[i % BufferSize] = i + 1;
qDebug() << "[生产者] 生产数据:" << i + 1
<< ",空闲位置:" << freeSlots.available()
<< ",已用位置:" << usedSlots.available();
// 释放已用位置
usedSlots.release(1); // 增加1个已用位置
}
}
};
// 消费者线程
class Consumer : public QThread {
protected:
void run() override {
for (int i = 0; i < 10; i++) { // 消费10个数据
// 等待已用位置
usedSlots.acquire(1); // 获取1个已用位置(若没有则阻塞)
// 从缓冲区取出数据
int data = buffer[i % BufferSize];
qDebug() << "[消费者] 消费数据:" << data
<< ",空闲位置:" << freeSlots.available()
<< ",已用位置:" << usedSlots.available();
// 释放空闲位置
freeSlots.release(1); // 增加1个空闲位置
}
}
};
// 执行结果:
生产者线程逻辑:freeSlots.acquire(1):尝试获取1个空闲位置。若缓冲区满(freeSlots.available() == 0),生产者阻塞。生产数据后,usedSlots.release(1):释放1个已用位置,通知消费者有数据可消费。
消费者线程逻辑:usedSlots.acquire(1):尝试获取1个已用数据。若缓冲区空(usedSlots.available() == 0),消费者阻塞。消费数据后,freeSlots.release(1):释放1个空闲位置,通知生产者有空间可生产。
条件变量
概念:条件变量(Condition Variable)是一种用于线程间同步的机制,它允许一个线程等待另一个线程的特定条件,直到该条件为真时才继续执行。条件变量提供了一种线程间通信的机制,使得线程可以等待某个条件的到来,然后再继续执行。
Qt提供了QWaitCondition类来实现条件变量,提供了以下基本操作:
wait(QMutex *mutex):使当前线程等待条件变量。线程在调用此方法时必须持有与条件变量关联的互斥锁。线程将释放互斥锁并进入等待状态,直到其他线程调用wakeOne()或wakeAll()方法唤醒它。wakeOne():唤醒一个等待条件变量的线程。被唤醒的线程将尝试重新获取互斥锁,并继续执行。wakeAll():唤醒所有等待条件变量的线程。所有被唤醒的线程将尝试重新获取互斥锁,并继续执行。
WakeOne例子
#include <QCoreApplication>
QMutex mutex; // 互斥锁
QWaitCondition condition; // 条件变量
bool ready = false; // 共享资源
// 线程1:等待数据准备完成
class Waiter : public QThread {
protected:
void run() override {
qDebug()<<"[等待线程]启动,等待数据准备...";
mutex.lock(); // 加锁
// 循环检查条件,防止虚假唤醒
while (!ready) { // 等待数据准备完成
qDebug()<<"[等待线程]数据未准备完成,等待...";
condition.wait(&mutex); // 释放mutex并进入等待状态,直到被唤醒
}
// 处理数据
mutex.unlock(); // 解锁
qDebug()<<"[等待线程]数据准备完成,继续执行!";
}
};
// 线程2:准备数据
class Preparer : public QThread {
protected:
void run() override {
qDebug()<<"[准备线程]启动,准备数据...";
// 准备数据
// ...
// 通知等待线程数据准备完成
mutex.lock(); // 加锁
ready = true; // 通知等待线程
condition.wakeOne(); // 唤醒等待线程
mutex.unlock(); // 解锁
qDebug()<<"[准备线程]数据准备完成,通知等待线程!";
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Waiter waiter;
Preparer preparer;
waiter.start(); // 启动等待线程
preparer.start(); // 启动准备线程
return a.exec();
}
// 执行结果:
[等待线程]启动,等待数据准备...
[等待线程]数据未准备完成,等待...
[准备线程]启动,准备数据...
[准备线程]数据准备完成,通知等待线程...
[等待线程]数据准备完成,继续执行!
关键机制说明:
- 互斥锁保护共享变量:
- dataReady 是被多个线程访问的共享变量,必须通过 QMutex 保护。
- 线程在修改或读取 dataReady 前必须先锁定 mutex
- 等待线程:
- 在 while (!dataReady) 循环中调用
dataPrepared.wait(&mutex),释放锁并进入等待状态。 - 被唤醒后重新获取锁,并再次检查条件(防止虚假唤醒)。
- 避免死锁:
- 触发线程在调用 wakeOne() 前必须持有锁,以确保修改 dataReady 的原子性。
- 等待线程在 wait() 返回后自动重新获取锁,保证后续操作的安全性。
WakeAll例子
#include <QCoreApplication>
#include <QWaitCondition>
#include <QMutex>
#include <QThread>
#include <QDebug>
QMutex mutex; // 保护共享资源的互斥锁
QWaitCondition taskAvailable; // 任务可用的条件变量
QList<int> tasks; // 任务队列
bool stopFlag = false; // 停止标志
// 工作线程(处理任务)
class Worker : public QThread {
int id; // 线程标识
public:
Worker(int id) : id(id) {}
protected:
void run() override {
qDebug() << "[工作线程" << id << "] 启动,等待任务...";
mutex.lock();
while (true) {
// 有任务时处理,否则等待
while (tasks.isEmpty() && !stopFlag) {
qDebug() << "[工作线程" << id << "] 无任务,进入等待...";
taskAvailable.wait(&mutex); // 释放锁并等待唤醒
}
// 检查是否退出
if (stopFlag) {
mutex.unlock();
break;
}
// 取出任务
if (!tasks.isEmpty()) {
int task = tasks.takeFirst();
mutex.unlock(); // 处理任务期间不需要持有锁
qDebug() << "[工作线程" << id << "] 处理任务:" << task;
QThread::sleep(1); // 模拟任务处理耗时
mutex.lock(); // 继续操作共享资源前重新加锁
}
}
qDebug() << "[工作线程" << id << "] 退出";
}
};
int main(int argc, char *argv[]) {
QCoreApplication a(argc, argv);
// 创建3个工作线程
Worker worker1(1), worker2(2), worker3(3);
worker1.start();
worker2.start();
worker3.start();
// 主线程添加任务
QThread::sleep(1); // 确保工作线程先进入等待状态
{
QMutexLocker locker(&mutex);
for (int i = 0; i < 5; i++) {
tasks.append(i + 1); // 添加5个任务
}
qDebug() << "[主线程] 发布5个任务,唤醒所有工作线程";
taskAvailable.wakeAll(); // 唤醒所有等待线程
}
// 等待任务处理完成
QThread::sleep(3); // 预留时间处理任务
// 通知工作线程退出
{
QMutexLocker locker(&mutex);
stopFlag = true;
taskAvailable.wakeAll(); // 再次唤醒所有线程以检查停止标志
}
worker1.wait();
worker2.wait();
worker3.wait();
qDebug() << "[主线程] 所有工作线程已退出";
return a.exec();
}
// 执行结果:
[工作线程1] 启动,等待任务...
[工作线程1] 无任务,进入等待...
[工作线程2] 启动,等待任务...
[工作线程2] 无任务,进入等待...
[工作线程3] 启动,等待任务...
[工作线程3] 无任务,进入等待...
[主线程] 发布5个任务,唤醒所有工作线程
[工作线程1] 处理任务:1
[工作线程2] 处理任务:2
[工作线程3] 处理任务:3
[工作线程1] 处理任务:4
[工作线程2] 处理任务:5
[工作线程1] 退出
[工作线程2] 退出
[工作线程3] 退出
[主线程] 所有工作线程已退出
关键机制说明:
主线程调用 taskAvailable.wakeAll() 后,所有等待的3个工作线程同时被唤醒。
每个被唤醒的线程会重新获取 mutex 锁,并检查 tasks.isEmpty() 条件:
第一个获取锁的线程(如线程1)发现 tasks 非空,取出任务1并处理。
第二个获取锁的线程(如线程2)发现 tasks 中仍有任务2,取出并处理。
依此类推,直到所有任务被处理完成。
线程竞争与任务分配:
多个线程被唤醒后,通过互斥锁 mutex 保证对共享资源 tasks 的原子访问。
任务分配是非确定性的,取决于线程调度(示例输出中线程1、2、3可能以任意顺序处理任务)。
停止机制:
主线程设置 stopFlag = true 后再次调用 wakeAll(),所有工作线程检查到停止标志后退出。
QT线程池
概念:线程池(Thread Pool)是一种多线程编程技术,它允许多个任务(线程)并行执行,从而提高程序的运行效率。它由一组预先创建的线程组成,可以在需要时分配任务给这些线程进行执行。使用线程池可以避免频繁地创建和销毁线程,从而减少线程创建和销毁的开销,并提高程序的性能和效率。
概述
Qt中提供了QThreadPool类来实现线程池。通过调用QThreadPool的start()方法,可以将一个QRunnable任务对象添加到线程池中进行执行。QThreadPool会根据设置的最大线程数和任务队列,自动选择合适的线程来执行任务。
- 任务队列,存储需要处理的任务,由工作的线程来处理这些任务
任务队列是一个先进先出的数据结构,用来存储需要处理的任务。
通过线程池提供的API函数,将一个待处理的任务添加到任务队列,或者从任务队列中删除
已处理的任务会被从任务队列中删除
线程池的使用者,也就是调用线程池函数往任务队列中添加任务的线程就是生产者线程 - 工作的线程(任务队列任务的消费者) ,N个:
线程池中维护了一定数量的工作线程, 他们的作用是是不停的读任务队列, 从里边取出任务并处理
工作的线程相当于是任务队列的消费者角色,
如果任务队列为空, 工作的线程将会被阻塞 (使用条件变量/信号量阻塞)
如果阻塞之后有了新的任务, 由生产者将阻塞解除, 工作线程开始工作 - 管理者线程(不处理任务队列中的任务),1个:
管理者线程的作用是管理线程池中的工作线程, 包括创建线程, 销毁线程, 监控线程的状态, 以及对任务队列中的任务进行分配
管理者线程的作用是为了保证线程池中的线程的健康运行, 包括线程的创建, 销毁, 以及线程的状态监控
管理者线程的作用是为了对任务队列中的任务进行分配, 也就是将任务分配给空闲的工作线程, 或者将任务重新分配给繁忙的工作线程
当任务过多的时候, 可以适当的创建一些新的工作线程
当任务过少的时候, 可以适当的销毁一些工作的线程
QRunnable 任务对象
QRunnable是一个基类,用于定义可在线程中执行的任务对象。通过继承QRunnable类并实现其run()函数,可以创建自定义的可执行任务对象。QRunnable可执行对象,可以被线程池多次调用、执行。
- 轻量级任务对象:相比
QThread,QRunnable更轻量,适合短任务 - 线程池支持:通过
QThreadPool管理,自动分配线程执行任务 - 自动删除选项:可通过
setAutoDelete()控制任务执行后的生命周期
#include <QCoreApplication>
#include <QThreadPool>
#include <QRunnable>
#include <QDebug>
#include <QThread>
#include <QTimer>
// 示例1: 基础任务(不使用信号槽)
class BasicTask : public QRunnable
{
public:
explicit BasicTask(int id) : m_id(id)
{
setAutoDelete(true); // 执行后自动删除
}
void run() override
{
qDebug() << "基础任务" << m_id << "在线程中执行:" << QThread::currentThreadId();
// 模拟工作
QThread::msleep(100);
}
private:
int m_id;
};
// 示例2: 支持信号槽的任务(需要继承 QObject)
class AdvancedTask : public QObject, public QRunnable
{
Q_OBJECT
public:
explicit AdvancedTask(int id, QObject *parent = nullptr)
: QObject(parent), m_id(id)
{
setAutoDelete(true);
}
void run() override
{
qDebug() << "高级任务" << m_id << "开始执行,线程:" << QThread::currentThreadId();
// 模拟耗时工作
for (int i = 0; i < 3; ++i) {
QThread::msleep(500);
emit progress(m_id, i + 1, 3); // 发送进度信号
}
emit finished(m_id, QString("任务完成,线程: %1").arg((quintptr)QThread::currentThreadId()));
}
signals:
void progress(int taskId, int current, int total);
void finished(int taskId, const QString& result);
private:
int m_id;
};
// 示例3: 带参数和返回值的任务
class DataProcessingTask : public QRunnable
{
public:
explicit DataProcessingTask(const QByteArray& data) : m_data(data)
{
setAutoDelete(true);
}
void run() override
{
qDebug() << "数据处理任务开始,数据大小:" << m_data.size();
// 模拟数据处理
QByteArray result = m_data.toBase64();
// 注意:不能直接返回值,需要通过其他机制传递结果
// 例如使用信号槽、回调函数或共享数据结构
}
private:
QByteArray m_data;
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug() << "主线程ID:" << QThread::currentThreadId();
// 获取全局线程池
QThreadPool* pool = QThreadPool::globalInstance();
qDebug() << "最大线程数:" << pool->maxThreadCount();
// 提交基础任务
for (int i = 0; i < 5; ++i) {
pool->start(new BasicTask(i));
}
// 创建高级任务对象(需要保持活跃以接收信号)
AdvancedTask* advancedTask = new AdvancedTask(100);
// 连接信号
QObject::connect(advancedTask, &AdvancedTask::progress, [](int taskId, int current, int total) {
qDebug() << "任务" << taskId << "进度:" << current << "/" << total;
});
QObject::connect(advancedTask, &AdvancedTask::finished, [](int taskId, const QString& result) {
qDebug() << "任务" << taskId << "完成:" << result;
});
// 提交高级任务
pool->start(advancedTask);
// 等待所有任务完成(可选)
pool->waitForDone();
qDebug() << "所有任务完成";
return 0;
}
- 多重继承的必要性
// 需要信号槽通信时
class TaskWithSignals : public QObject, public QRunnable
{
Q_OBJECT
// ...
};
// 不需要信号槽时(更轻量)
class SimpleTask : public QRunnable
{
// ...
};
-
QRunnable提供run()方法和线程池集成 -
QObject提供信号槽机制、对象树管理等功能 -
两者结合既能让任务在线程池中执行,又能使用 Qt 的事件系统
- 自动删除机制
// 自动删除(推荐用于一次性任务)
setAutoDelete(true);
// 任务执行完毕后会自动删除,无需手动管理
// 手动删除(适用于可重用的任务对象)
setAutoDelete(false);
// 需要手动管理生命周期,可在多次执行间保持状态
- 任务结果的传递方式
由于run()方法没有返回值,需要通过其他机制传递结果。
- 信号槽机制:通过信号槽传递结果
- 回调函数:通过回调函数传递结果
- 共享数据结构:通过共享数据结构传递结果(需要线程同步)
- QFuture 和 QtConcurrent:通过
QFuture传递结果(推荐)
注意:
- 线程安全考虑:如果任务需要访问共享资源,则需要考虑线程同步。
- 资源管理:如果任务需要使用资源,则需要在任务执行完毕后释放资源。
// 如果任务中创建了需要清理的资源
void run() override
{
// 获取资源
QResource* resource = acquireResource();
try {
// 使用资源工作
processWithResource(resource);
} catch (...) {
// 异常处理
}
// 确保资源释放(即使发生异常)
releaseResource(resource);
}
QThreadPool 线程池
Qt中的QThreadPool类管理了一组 QThreads, 里边还维护了一个任务队列。QThreadPool 管理和回收各个 QThread 对象,以帮助减少使用线程的程序中的线程创建成本。每个Qt应用程序都有一个全局QThreadPool对象,可以通过调用 globalInstance() 来访问它。也可以单独创建一个 QThreadPool 对象使用, 但这种方式不推荐。
线程池常用API函数
-
start(QRunnable *runnable):将一个QRunnable对象添加到线程池中,并启动线程执行该任务。 -
tryStart(QRunnable *runnable):尝试将一个QRunnable对象添加到线程池中,但不启动线程执行该任务。如果线程池中有空闲线程,则启动线程执行该任务,否则返回 false。 -
void setMaxThreadCount(int maxThreadCount):设置线程池中最大线程数。 -
int maxThreadCount():获取线程池中最大线程数。 -
int activeThreadCount():获取线程池中活动线程数。 -
bool tryTake(QRunnable *runnable):尝试从线程池中取出一个QRunnable对象。如果线程池中有空闲线程,则返回true,如果任务已经开始执行就无法删除,返回false。 -
void clear():将线程池中的任务队列里边没有开始处理的所有任务删除, 如果已经开始处理了就无法通过该函数删除 -
static QThreadPool* globalInstance():获取全局的QThreadPool对象。
线程池的使用
#include <QCoreApplication>
#include <QThreadPool>
#include <QRunnable>
#include <QDebug>
mywork.h
class MyWork :public QRunnable
{
Q_OBJECT
public:
explicit MyWork();
~MyWork();
void run() override;
}
mywork.cpp
MyWork::MyWork() : QRunnable()
{
// 任务执行完毕,该对象自动销毁
setAutoDelete(true);
}
void MyWork::run()
{
// 业务处理代码
......
}
mainwindow.cpp
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 线程池初始化,设置最大线程池数
QThreadPool::globalInstance()->setMaxThreadCount(4);
// 提交10个任务到线程池
for (int i = 0; i < 10; ++i) {
QThreadPool::globalInstance()->start(new MyTask(i));
}
qDebug() << "所有任务已提交,主线程继续运行...";
// 等待所有任务完成
QThreadPool::globalInstance()->waitForDone();
qDebug() << "所有任务已完成";
}
关键机制解析:
- 在
mainwindow.cpp中,创建了一个QThreadPool对象,并设置最大线程数为4。 - 在
mainwindow.cpp中,创建10个MyTask对象,并提交到线程池中。 - 在
mainwindow.cpp中,等待所有任务完成。 - 在
MyTask类中,实现了run()函数,在其中执行了一些业务处理代码。 - 在
MyTask类中,通过setAutoDelete(true)设置该对象在线程池中的线程中处理完毕后自动销毁。
QtConcurrent +QFuture简化操作
使用QtConcurrent::run 提交任务到线程中去执行。
函数原型:
QFuture QtConcurrent::run(QThreadPool *pool, Function function, …)
- 参数 function : 表示要在线程中执行的函数。
- 参数 pool :线程池。表示从线程池中获取一个线程来执行该函数。
- 注意 :函数可能不会立即执行;一旦线程池中的线程有可用的线程时,才会被执行。
- 返回值 :返回一个 QFuture<T> 对象。后面会详细说明。
当不传入线程池地址时,则调用重载的这个版本:QFuture QtConcurrent::run(Function function, …)
表示从全局的线程池中,获取线程。
调用方式
- 普通函数调用
int computeSquare(int n) {
qDebug() << "计算平方" << n << "在线程" << QThread::currentThreadId();
QThread::msleep(1000); // 模拟耗时操作
return n * n;
}
- Lambda 表达式
auto computeCube = [](int n) {
qDebug() << "计算立方" << n << "在线程" << QThread::currentThreadId();
QThread::msleep(1000);
return n * n * n;
};
- 类成员函数
class MathUtils {
public:
double computeSqrt(double n) {
qDebug() << "计算平方根" << n << "在线程" << QThread::currentThreadId();
QThread::msleep(1000);
return sqrt(n);
}
};
- 带状态的对象函数
class StatefulComputer {
public:
StatefulComputer() : callCount(0) {}
QString process(const QString& input) {
callCount++;
qDebug() << "处理" << input << "调用次数:" << callCount << "在线程" << QThread::currentThreadId();
QThread::msleep(500);
return input.toUpper();
}
private:
int callCount;
};
int main(int argc, char *argv[]) {
QCoreApplication a(argc, argv);
qDebug() << "主线程ID:" << QThread::currentThreadId();
// 1. 使用普通函数
QFuture<int> squareFuture = QtConcurrent::run(computeSquare, 5);
// 2. 使用Lambda表达式
QFuture<int> cubeFuture = QtConcurrent::run(computeCube, 5);
// 3. 使用类成员函数
MathUtils mathUtils; // 需要取得对象实例
QFuture<double> sqrtFuture = QtConcurrent::run(&mathUtils, &MathUtils::computeSqrt, 25.0);
// 4. 使用自定义线程池
QThreadPool customPool;
customPool.setMaxThreadCount(2);
StatefulComputer computer;
QFuture<QString> processFuture = QtConcurrent::run(&customPool, &computer, &StatefulComputer::process, "hello");
// 5. 使用QFutureWatcher监控任务完成
QFutureWatcher<int> squareWatcher;
QObject::connect(&squareWatcher, &QFutureWatcher<int>::finished, []() {
qDebug() << "平方计算完成";
});
squareWatcher.setFuture(squareFuture);
// 等待所有任务完成
squareFuture.waitForFinished();
cubeFuture.waitForFinished();
sqrtFuture.waitForFinished();
processFuture.waitForFinished();
// 获取结果
qDebug() << "5的平方:" << squareFuture.result();
qDebug() << "5的立方:" << cubeFuture.result();
qDebug() << "25的平方根:" << sqrtFuture.result();
qDebug() << "处理结果:" << processFuture.result();
return 0;
}
QFuture 类型
QFuture<T> 是一个模板类,表示一个异步执行的任务的结果。T 表示任务的返回值类型。QFuture<T>作为异步任务的句柄,用于获取任务返回值、查询状态(如是否完成)或等待任务结束。
QFuture 类提供了以下几个主要的成员函数:
- 任务状态查询:
bool isStarted() const: 回任务是否已开始执行。bool isFinished() const: 回任务是否已完成。bool isRunning() const: 回任务是否正在运行。bool isCanceled() const: 回任务是否已被取消
- 结果获取:
T result() const: 获取任务的返回结果(T 为返回值类型)。若任务未完成,会阻塞直到结果可用。T resultAt(int index) const: 获取多结果任务中第 index 个结果。int resultCount() const: 返回已完成结果的数量(适用于多结果任务)。QList<T> results() const: 获取所有结果组成的列表。
- 等待任务完成:
void waitForFinished(): 当前线程,直到任务完成(或超时)。bool waitForFinished(int timeout): 阻塞当前线程最多 timeout 毫秒,超时返回 false。
- 任务控制:
void cancel(): 取消任务(若任务支持取消)。void pause(): 暂停任务(需任务内部支持暂停逻辑)。void resume(): 恢复已暂停的任务。
- 进度监控:
int progressValue() const: 获取当前进度值(需任务内部更新进度)。int progressMinimum() const: 获取最小进度值。int progressMaximum() const: 获取最大进度值。
Qt 多线程编程方案对比总结
| 特性 | QThread | QRunnable + QThreadPool | QtConcurrent + QFuture |
|---|---|---|---|
| 基本概念 | 线程类,封装了平台线程 | 任务接口,配合线程池使用 | 高级API,函数式并发编程 |
| 使用方式 | 继承或使用moveToThread | 继承QRunnable实现run() | 调用QtConcurrent::run() |
| 创建成本 | 较高(每个线程独立资源) | 较低(线程复用) | 最低(完全自动管理) |
| 生命周期 | 手动管理线程启动和停止 | 自动或手动管理任务对象 | 完全自动管理 |
| 任务通信 | 信号槽(需事件循环) | 需要额外机制(如信号槽需继承QObject) | 通过QFuture获取结果 |
| 进度反馈 | 可通过自定义信号实现 | 需要自定义实现 | 内置进度报告机制 |
| 异常处理 | 需要手动处理 | 需要手动处理 | 可通过QFuture捕获异常 |
| 取消支持 | 需要自定义实现 | 需要自定义实现 | 内置取消机制 |
| 适用场景 | 长时运行任务、需要事件循环的任务 | 短时任务、高并发任务 | 函数式任务、数据并行处理 |
| 代码复杂度 | 高 | 中 | 低 |
| 控制粒度 | 线程级别 | 任务级别 | 函数级别 |
| 线程安全 | 需要开发者保证 | 需要开发者保证 | API本身是线程安全的 |
案例说明
- QThread 示例
// 方式1:继承QThread
class WorkerThread : public QThread {
void run() override {
// 长时间运行的任务
while (!isInterruptionRequested()) {
// 处理工作
QThread::sleep(1);
}
}
};
// 方式2:使用moveToThread
QThread* thread = new QThread;
Worker* worker = new Worker;
worker->moveToThread(thread);
connect(thread, &QThread::started, worker, &Worker::doWork);
thread->start();
- QRunnable + QThreadPool 示例
class Task : public QRunnable {
void run() override {
// 执行任务
qDebug() << "Task running in thread:" << QThread::currentThreadId();
}
};
// 提交任务到线程池
Task* task = new Task;
task->setAutoDelete(true); // 自动删除
QThreadPool::globalInstance()->start(task);
- QtConcurrent + QFuture 示例
// 简单函数调用
QFuture<int> future = QtConcurrent::run([]() {
return computeResult();
});
// 使用QFutureWatcher监控进度
QFutureWatcher<int> watcher;
connect(&watcher, &QFutureWatcher<int>::finished, []() {
qDebug() << "Result:" << watcher.result();
});
watcher.setFuture(future);
// 批量处理
QList<int> inputs = {1, 2, 3, 4, 5};
QList<QFuture<int>> results;
for (int input : inputs) {
results.append(QtConcurrent::run(processInput, input));
}
综合建议
-
对于大多数应用场景,优先考虑 QtConcurrent,它提供了最简单安全的并发编程方式
-
当 QtConcurrent 无法满足需求时,考虑使用 QRunnable + QThreadPool
-
只有在需要复杂线程控制或事件循环时,才使用 QThread
-
在实际项目中,这三种方式可以混合使用,根据任务特点选择最合适的方案

浙公网安备 33010602011771号