1. Qt多线程与Qobject的关系
每一个 Qt 应用程序至少有一个事件循环,就是调用了QCoreApplication::exec()的那个事件循环。不过,QThread也可以开启事件循环。只不过这是一个受限于线程内部的事件循环。因此我们将处于调用main()函数的那个线程,并且由QCoreApplication::exec()创建开启的那个事件循环成为主事件循环,或者直接叫主循环。注意,QCoreApplication::exec()只能在调用main()函数的线程调用。主循环所在的线程就是主线程,也被成为 GUI 线程,因为所有有关 GUI 的操作都必须在这个线程进行。QThread的局部事件循环则可以通过在QThread::run()中调用QThread::exec()开启:
class Thread : public QThread { protected: void run() { } };
注意:Qt 4.4 版本以后,QThread::run()不再是纯虚函数,它会调用QThread::exec()函数。与QCoreApplication一样,QThread也有QThread::quit()和QThread::exit()函数来终止事件循环。
run 函数是做什么用的?Manual中说的清楚:
run 对于线程的作用相当于main函数对于应用程序。它是线程的入口,run的开始和结束意味着线程的开始和结束。
The run() implementation is for a thread what the main() entry point is for the application. All code executed in a call stack that starts in the run() function is executed by the new thread, and the thread finishes when the function returns.
线程的事件循环用于为线程中的所有QObjects对象分发事件;默认情况下,这些对象包括线程中创建的所有对象,或者是在别处创建完成后被移动到该线程的对象(我们会在后面详细介绍“移动”这个问题)。我们说,一个QObject的所依附的线程(thread affinity)是指它所在的那个线程。它同样适用于在QThread的构造函数中构建的对象:
class MyThread : public QThread { public: MyThread() { otherObj = new QObject; } private: QObject obj; QObject *otherObj; QScopedPointer yetAnotherObj; };
上面线程对象中的子成员:obj, 以及otherObj所指向的对象,以及yetAnotherObj,都在使创建Mytherad的线程,即主线程,而不是子线程。
在我们创建了MyThread对象之后,obj、otherObj和yetAnotherObj的线程依附性是怎样的?是不是就是MyThread所表示的那个线程?要回答这个问题,我们必须看看究竟是哪个线程创建了它们:实际上,是调用了MyThread构造函数的线程创建了它们。因此,这些对象不在MyThread所表示的线程,而是在创建了MyThread的那个线程中。
(1)QObject::connect
涉及信号槽,我们就躲不过 connect 函数,只是这个函数大家太熟悉。我不好意思再用一堆废话来描述它,但不说又不行,那么折中一下,只看它的最后一个参数吧(为了简单起见,只看它最常用的3个值):
通过指定connect的连接方式,如果指定直接连接(Direct Connection),则该槽函数将再信号发出的线程中直接执行,而不用判定当前信号发出的线程与槽函数所在线程的状态;如果指定队列连接(Queued Connection),则该槽函数在接受者所依附的线程的线程循环中被指定调用;如果为自动连接(Auto Connection)需要判定发射信号的线程和接受者所依附的线程是否相同,进行细分指定。
-
自动连接(Auto Connection)
- 这是默认设置
- 如果发送者的信号(不是发送者对象)在接收者所依附的线程内发射,则等同于直接连接
- 如果发射信号的线程和接受者所依附的线程不同,则等同于队列连接
- 也就是这说,只存在下面两种情况
-
直接连接(Direct Connection)
- 当信号发射时,槽函数将直接被调用。
- 无论槽函数所属对象在哪个线程,槽函数都在发射信号的线程内执行。
-
队列连接(Queued Connection)
- 当控制权回到接受者所依附线程的事件循环时,槽函数被调用。
- 槽函数在接收者所依附线程执行。
(2)qT线程管理的原则:
- QThread 是用来管理线程的,它所依附的线程和它管理的线程并不是同一个东西
- QThread 所依附的线程,就是执行 QThread t 或 QThread * t=new QThread 所在的线程。也就是咱们这儿的主线程
- QThread 管理的线程,就是 run 启动的线程。也就是次线程
- 因为QThread的对象依附在主线程中,所以他的slot函数会在主线程中执行,而不是次线程。除非:
- QThread 对象依附到次线程中(通过movetoThread)
- slot 和信号是直接连接(通过connect连接方式来指定),且信号在次线程中发射
【1】主线程(信号)QThread(槽)
class Dummy:public QObject { Q_OBJECT public: Dummy(){} public slots: void emitsig() { emit sig(); } signals: void sig(); }; class Thread:public QThread { Q_OBJECT public: Thread(QObject* parent=0):QThread(parent) { //moveToThread(this); } public slots: void slot_main() { qDebug()<<"from thread slot_main:" <<currentThreadId(); } protected: void run() { qDebug()<<"thread thread:"<<currentThreadId(); exec(); } }; #include "main.moc" int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); qDebug()<<"main thread:"<<QThread::currentThreadId(); Thread thread; //槽函数所在的对象依附于线程, Dummy dummy; QObject::connect(&dummy, SIGNAL(sig()), &thread, SLOT(slot_main())); //采用默认的链接方式 thread.start(); dummy.emitsig();//信号在主线程中发射 return a.exec(); }
程序运行结果:
main thread: 0x1a40
from thread slot_main: 0x1a40
thread thread: 0x1a48
因为connect采用默认的链接方式,则需要判定发射信号的线程和接受者所依附的线程是否相同,信号在主线程中发射 且槽函数所在的对象依附于线程, 因此链接方式是直接连接,从而运行结果是:槽函数的线程Id和主线程ID是一样的!
因为slot和run处于不同线程,需要线程间的同步!
你会发现 QThread 中 slot 和 run 函数共同操作的对象,都会用QMutex锁住。因为此时run 是另一个线程,即子线程。而slot则是在主线程执行。必须适应锁来保证数据同步。
如果想让槽函数slot在次线程运行(比如它执行耗时的操作,会让主线程卡死)
- 将 thread 依附的线程改为次线程不就行了,这也是代码中注释掉的 moveToThread(this)所做的
去掉注释,你会发现slot在次线程中运行结果:
main thread: 0x13c0
thread thread: 0x1de0
from thread slot_main: 0x1de0
但这是 Bradley T. Hughes 强烈批判的用法。不推荐这样使用。
【2】run中信号与QThread中槽
即,信号在子线程发射,而槽函数谁线程的槽函数,而线程对象在主线程中创建,如果链接采用自动链接,则条件判断比为队列连接,且由主线程在主线程的事件循环中执行。如下所示:
class Dummy:public QObject { Q_OBJECT public: Dummy(QObject* parent=0):QObject(parent){} public slots: void emitsig() { emit sig(); } signals: void sig(); }; class Thread:public QThread { Q_OBJECT public: Thread(QObject* parent=0):QThread(parent) { //moveToThread(this); } public slots: void slot_thread() { qDebug()<<"from thread slot_thread:" <<currentThreadId(); } signals: void sig(); protected: void run() { qDebug()<<"thread thread:"<<currentThreadId(); Dummy dummy; connect(&dummy, SIGNAL(sig()), this, SLOT(slot_thread())); dummy.emitsig(); exec(); } }; #include "main.moc" int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); qDebug()<<"main thread:"<<QThread::currentThreadId(); Thread thread; thread.start(); return a.exec(); }