线程可以把耗时工作与 GUI 事件处理分开,也可能利用多个 CPU 核心并行计算。是否更快取决于任务、同步与调度成本;创建更多线程并不保证更快。框架可能内部使用线程池,但普通函数调用和普通信号连接不会因为“耗时”就自动创建线程。
本页学习 QThread 的执行和回收边界;需要长期接收任务的 QObject 模式见 Worker Object。
三个必须区分的对象
跳转到“三个必须区分的对象”| 名称 | 本例所在位置 | 作用 |
|---|---|---|
| QThread 对象 | 主线程 | 管理线程启动、结束及相关信号 |
| 被管理的线程 | 后台线程 | 执行重写的 run |
| 主线程事件循环 | 主线程 | 接收结果,处理输入和显示 |
start() 才启动新的执行线程;直接调用 run() 只是当前线程上的普通函数调用。重写 run 而不调用 exec() 时,该后台线程没有 Qt 事件循环,run 返回就结束。QThread
完整例子:后台求和,主线程接收结果
跳转到“完整例子:后台求和,主线程接收结果”// main.cpp;Qt6::Core,CMAKE_AUTOMOC=ON#include <QCoreApplication>#include <QThread>#include <cassert>
class SumThread final : public QThread { Q_OBJECTsignals: void resultReady(int sum);protected: void run() override { int sum = 0; for (int i = 1; i <= 100; ++i) { if (isInterruptionRequested()) return; sum += i; } emit resultReady(sum); }};
int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); SumThread thread; int received = -1; QObject::connect(&thread, &SumThread::resultReady, &app, [&](int sum) { assert(QThread::currentThread() == app.thread()); received = sum; }, Qt::QueuedConnection); QObject::connect(&thread, &QThread::finished, &app, &QCoreApplication::quit, Qt::QueuedConnection); thread.start(); const int exitCode = app.exec(); thread.requestInterruption(); thread.quit(); const bool joined = thread.wait(); assert(joined); assert(received == 5050); return exitCode;}
#include "main.moc"程序只进入一次应用事件循环。它等待 finished 通知退出循环,然后确认线程结束再销毁栈上的 QThread。不能在 start() 后立刻 wait() 并期待 GUI 同时响应;那会把调用 wait 的主线程阻塞住。
received 只在主线程槽中写入,因此不用为了接收这个结果额外加锁。任务的中间变量 sum 留在后台线程中;信号把最终整数交给主线程,不暴露正在修改的共享容器。
最小 CMake 配置:
cmake_minimum_required(VERSION 3.20)project(ThreadExample LANGUAGES CXX)set(CMAKE_CXX_STANDARD 17)set(CMAKE_AUTOMOC ON)find_package(Qt6 REQUIRED COMPONENTS Core)add_executable(ThreadExample main.cpp)target_link_libraries(ThreadExample PRIVATE Qt6::Core)图像处理时怎样更新界面
跳转到“图像处理时怎样更新界面”把读取、计算和推理放进后台任务,向主线程发送结果或进度;主线程的槽再修改控件。不能仅仅把 YOLOWINDOW::processImages() 写成成员函数并发出信号,就假定这个函数已经在后台执行。
通常在后台使用 QImage 保存像素数据,在 GUI 线程转成 QPixmap 并设置到 QLabel。跨线程发送的对象要有合适的复制和元类型支持,接收者需要活着且能处理事件;发送裸指针并不会自动延长其指向对象的寿命。Qt 线程与对象、绘图和线程
停止任务的契约
跳转到“停止任务的契约”中断是请求,任务应在合适位置检查并尽快返回。请求不能强行打断长时间阻塞的第三方推理或 I/O 函数,因此真实程序还要给这类调用设计超时或取消方法。窗口关闭时应先请求停止,确认工作线程结束,再释放其使用的模型、数据和接收者。
需要在后台处理定时器、异步 socket 或连续到来的槽调用时,使用 Worker Object 的事件循环模型,而不是在没有事件循环的 run 中等待这些事件。