QThread非循环无限生命周期下的两类使用疑问咨询
常规QThread使用方式理解
我对QThread的常规使用方式理解如下,示例代码:
MyWorker *worker = new MyWorker(); QThread *thread = new QThread(); worker->moveToThread(thread); connect(thread, SIGNAL(started()), worker, SLOT(doWork())); connect(worker, SIGNAL(done()), thread, SLOT(quit())); connect(worker, SIGNAL(done()), worker, SLOT(deleteLater())); connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); thread->start();
这段代码会让MyWorker的doWork()方法在thread线程中执行,当worker通过done()信号告知完成后,线程停止并清理内存,适用于Worker在独立线程完成单次操作的场景。
基于此,我总结的Qt线程相关规则:
- 若信号与槽的对象同线程,
Qt::AutoConnection会使用Qt::DirectConnection - 若信号与槽的对象不同线程,
Qt::AutoConnection会使用Qt::QueuedConnection Qt::DirectConnection会在信号发出线程执行目标槽函数Qt::QueuedConnection会将信号入队,最终在槽对象所属线程的事件循环中执行moveToThread()会将对象从当前线程迁移到指定线程- QThread会保持活跃直到调用
QThread::quit(),因此上述代码中连接quit()的操作至关重要
两类非范式场景疑问
疑问1:仅将对象移至新线程,其槽函数是否会在新线程中执行?
示例代码:
MyWorker *worker = new MyWorker(); QThread *workerThread = new QThread(); worker->moveToThread(workerThread); connect(workerThread, SIGNAL(finished()), worker, SLOT(deleteLater())); connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater())); workerThread->start();
此代码中MyWorker无专门的执行槽,若在其他代码中执行:
MyTrigger trigger; connect(&trigger, SIGNAL(triggered()), worker, SLOT(doSomething()));
当trigger发出triggered()信号时,worker的doSomething()槽是否会在workerThread中执行,而非trigger所属线程?即无需显式doWork()也能实现槽函数的多线程处理。
疑问2:对象创建时所属线程如何确定?能否确保对象在指定线程创建?
我希望创建一个在独立线程中运行的完整功能集合,示例代码:
class MyCapability: public QObject { Q_OBJECT public: MyOtherWorker* getOtherWorker() { return worker2; } public slots: void setup() { worker1 = new MyWorker(); worker2 = new MyOtherWorker(); connect(worker1, SIGNAL(something()), worker2, SLOT(other())); } void doSomething() { worker1->doSomething(); } private: MyWorker *worker1; MyOtherWorker *worker2; }; MyCapability *capability = new MyCapability(); QThread *capabilityThread = new QThread(); capability->moveToThread(capabilityThread); connect(capabilityThread, SIGNAL(started()), capability, SLOT(setup())); connect(capabilityThread, SIGNAL(finished()), capability, SLOT(deleteLater())); connect(capabilityThread, SIGNAL(finished()), capabilityThread, SLOT(deleteLater())); capabilityThread->start();
我的理解:
capability会处于capabilityThread线程中- 由于
MyCapability::setup()在capabilityThread中调用,其中创建的对象也会属于capabilityThread worker1::something()与worker2::other()的连接为Qt::DirectConnection,因为二者同线程
当外部代码执行:
MyTrigger trigger; connect(&trigger, SIGNAL(doSomethingTrigger()), capability, SLOT(doSomething())); connect(&trigger, SIGNAL(doOtherTrigger()), capability->getOtherWorker(), SLOT(other()));
若trigger在不同线程,连接会使用Qt::QueuedConnection,因为capability、worker1、worker2都在capabilityThread中。
我希望通过这种方式创建一个独立于主应用线程的功能单元,让主应用无需关注线程细节,通过信号槽安全可靠地执行处理。
请问我的上述两类理解是否正确?存在哪些错误假设?
解答
针对疑问1的回答
你的理解是正确的。
当trigger发出信号时,由于worker已经通过moveToThread()归属到workerThread,且使用默认的Qt::AutoConnection,此时信号发送者(trigger)和接收者(worker)分属不同线程,连接会自动转为Qt::QueuedConnection,槽函数doSomething()会在workerThread的事件循环中执行。
只要workerThread已经启动(调用了start())且运行着事件循环,所有发送给worker的信号对应的槽函数都会在workerThread中执行,不需要显式的doWork()入口槽。
针对疑问2的回答
你的核心理解基本正确,但存在一处需要注意的细节:
capability的线程归属:正确,moveToThread()后capability的线程所属为capabilityThread,其槽函数(如setup()、doSomething())会在该线程执行。worker1、worker2的线程归属:正确。QObject的线程归属由创建它的线程决定(更准确地说,是创建时当前调用线程的QThread对象),setup()在capabilityThread中执行,所以worker1和worker2的线程所属为capabilityThread。worker1与worker2的连接类型:正确。二者同线程,默认的Qt::AutoConnection会转为Qt::DirectConnection,槽函数other()会在worker1发出信号的线程(即capabilityThread)中直接执行。- 外部与
capability、worker2的连接:正确。若trigger在其他线程,信号发送者与接收者分属不同线程,Qt::AutoConnection会转为Qt::QueuedConnection,槽函数会在capabilityThread的事件循环中执行,主应用无需手动处理线程同步。
潜在的错误假设/注意事项
- 线程事件循环的必要性:所有依赖
Qt::QueuedConnection的槽函数执行,都要求接收者所属线程必须运行事件循环。你的代码中workerThread和capabilityThread都调用了start(),默认会启动事件循环,这没问题;但如果手动重写run()且没启动事件循环,则队列连接的槽函数无法执行。 - 对象创建的线程上下文:如果
setup()不是通过信号槽触发(而是直接在主线程调用),那么worker1和worker2会归属主线程,破坏设计。必须确保setup()在capabilityThread的上下文执行,目前通过connect(capabilityThread, SIGNAL(started()), capability, SLOT(setup()))的方式是安全的。 deleteLater()的线程安全:你的代码中connect(workerThread, SIGNAL(finished()), worker, SLOT(deleteLater()))是安全的,deleteLater()会将对象的销毁事件加入接收者所属线程的事件循环,确保对象在自身线程中被销毁,避免跨线程直接析构的问题。- 代码笔误修正:原示例代码中有两处笔误:
connect(workerThread, SIGNAL(finished()), thread, SLOT(deleteLater()))中的thread应该替换为对应线程对象(workerThread或capabilityThread),否则会导致未定义行为。
内容的提问来源于stack exchange,提问作者cancech

