在 Qt 中,多线程开发主要围绕 QThread(底层控制)和 QThreadPool(效率复用)展开。
-
QThread
:手动管理线程生命周期,适用于常驻后台任务。
-
QThreadPool
:自动管理线程池,适用于大量短时、并行的耗时任务。
三种实现方案对比
| 特性 | moveToThread (推荐) | 子类化 QThread | 线程池 (QThreadPool) |
|---|---|---|---|
| 生命周期 | 手动管理 | 手动管理 | 自动回收 |
| 开销 | 较高 (每任务一线程) | 较高 | 极低 (线程复用) |
| 解耦度 | 极高 | 低 | 高 |
| 适用场景 | 网络通信、常驻监控 | 底层控制、死循环 | 扫描任务、高并发计算 |
一、 线程生命周期与控制 (QThread)
1. 启动与退出
-
start():启动线程,触发
started()信号。 -
exit(int)/
quit():告诉线程的事件循环退出。 -
terminate():危险操作! 强制终止线程,可能导致资源未释放或死锁。
2. 状态与内存
-
isRunning()/
isFinished():查询状态。 -
wait():阻塞等待线程结束。
-
自动释放
:建议
connect(thread, &QThread::finished, thread, &QObject::deleteLater);
二、 方案一:moveToThread (推荐)
核心思想 :通过 moveToThread 将逻辑类(Worker)推向指定的 QThread。
cpp
// 在控制器中应用
Worker *worker = new Worker;
worker->moveToThread(&workerThread);
connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
connect(this, &Controller::operate, worker, &Worker::doWork);
workerThread.start();
三、 方案二:子类化 QThread (传统)
核心思想 :重写 run() 函数。注意:QThread 对象本身在旧线程 ,只有 run() 内部在新线程。
cpp
classWorkerThread : public QThread {
voidrun()override{
// 新线程执行逻辑
emit resultReady("done");
exec(); // 开启事件循环
}
};
四、 技术深究:事件循环与异步通知
1. 跨线程信号槽
Qt 默认使用 Queued Connection:信号发送后入队,接收者在自己的线程循环中异步执行槽函数。
2. 高频数据通知
对于高并发采集,可结合 QWaitCondition 与 QApplication::postEvent() 实现生产者-消费者模式,平衡采集与处理的压力。
五、 方案三:线程池 (QThreadPool)
核心思想:在程序启动时创建一组线程重复使用,避免频繁创建/销毁线程的系统开销。
1. 核心用法:QRunnable
要使用线程池,需要子类化 QRunnable 并实现 run()。
cpp
classMyTask : public QRunnable {
voidrun()override{
qDebug() << "任务在线程" << QThread::currentThreadId() << "运行";
}
};
// 提交任务
QThreadPool::globalInstance()->start(newMyTask());
2. 管理与优化
-
全全局实例
:
QThreadPool::globalInstance()访问预定义的全局池。 -
自动删除
:
QThreadPool默认会自动delete已完成的QRunnable。 -
限额控制
:
setMaxThreadCount()设置并发上限(默认值为 CPU 核心数)。 -
过期回收
:闲置 30 秒后的线程会自动销毁(可通过
setExpiryTimeout修改)。
3. 实战案例:IP 地址扫描器
cpp
classScanIpTask : public QRunnable {
public:
QString ip;
ScanIpTask(QString addr) : ip(addr) {}
voidrun()override{
int exitCode = QProcess::execute("ping", {"-n", "1", ip});
qDebug() << ip << (exitCode == 0 ? "存活" : "无法访问");
}
};
// 批量提交
for(int i=0; i<255; i++) {
auto task = newScanIpTask(QString("192.168.1.%1").arg(i));
QThreadPool::globalInstance()->start(task);
}
六、 实战建议与注意事项
1. UI 限制
-
严禁
:在非 GUI 线程直接操作界面控件。
2. 资源安全
- 多线程共享数据时,必须使用
QMutex、QReadWriteLock或QSemaphore进行保护。
3. 工具备忘
-
QThread::currentThreadId():定位当前线程。
-
QThread::idealThreadCount():获取 CPU 理想并发数。