解析Qt中QThread使用方法
本文講述的是在Qt中QThread使用方法,QThread似乎是很難的一個(gè)東西,特別是信號(hào)和槽,有非常多的人(盡管使用者本人往往不知道)在用不恰當(dāng)(甚至錯(cuò)誤)的方式在使用 QThread,隨便用google一搜,就能搜出大量結(jié)果出來(lái)。無(wú)怪乎Qt的開(kāi)發(fā)人員 Bradley T. Hughes 聲嘶力竭地喊you are-doing-it-wrong
和眾多用戶一樣,初次看到這個(gè)時(shí),感到 Bradley T. Hughes有 些莫名奇妙,小題大作。盡管不舒服,當(dāng)時(shí)還是整理過(guò)一篇博客QThread 的使用方法
時(shí)間過(guò)去3個(gè)月,盡管依然沒(méi)怎么用thread;但今天csdn論壇中有人問(wèn)到這個(gè)問(wèn)題,想想還是盡我所能整理一下吧。提升自己,方便他人,何樂(lè)而不為呢?
QThread東西還是比較多的,而且我對(duì)底層對(duì)象了解有限,僅就一點(diǎn)進(jìn)行展開(kāi)(或許是大家最關(guān)心的一點(diǎn)):QThread中的slots在那個(gè)線程中執(zhí)行?
QThread::run
run 函數(shù)是做什么用的?Manual中說(shuō)的清楚:
run 對(duì)于線程的作用相當(dāng)于main函數(shù)對(duì)于應(yīng)用程序。它是線程的入口,run的開(kāi)始和結(jié)束意味著線程的開(kāi)始和結(jié)束。 原文如下:
- 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.
這么短的文字一眼就看完了,可是,這是什么意思呢?又能說(shuō)明什么問(wèn)題呢?看段簡(jiǎn)單代碼:
- class Thread:public QThread {
- Q_OBJECT public:
- Thread(QObject* parent=0):QThread(parent){}
- public slots:
- void slot() { ... } signals:
- void sig(); protected:
- void run() { ...} };
- int main(int argc, char** argv) { ... Thread thread; ... }
對(duì)照前面的定理,run函數(shù)中的代碼時(shí)確定無(wú)疑要在次線程中運(yùn)行的,那么其他的呢?比如 slot 是在次線程還是主線程中運(yùn)行?
QObject::connect
涉及信號(hào)槽,我們就躲不過(guò) connect 函數(shù),只是這個(gè)函數(shù)大家太熟悉。我不好意思再用一堆廢話來(lái)描述它,但不說(shuō)又不行,那么折中一下,只看它的***一個(gè)參數(shù)吧(為了簡(jiǎn)單起見(jiàn),只看它最常用的3個(gè)值):
1、自動(dòng)連接(Auto Connection)
這是默認(rèn)設(shè)置
如果發(fā)送者和接收者處于同一線程,則等同于直接連接
如果發(fā)送者和接受者位于不同線程,則等同于隊(duì)列連接
也就是這說(shuō),只存在下面兩種情況
2、直接連接(Direct Connection)
當(dāng)信號(hào)發(fā)射時(shí),槽函數(shù)將直接被調(diào)用。
無(wú)論槽函數(shù)所屬對(duì)象在哪個(gè)線程,槽函數(shù)都在發(fā)射者所在線程執(zhí)行。
3、隊(duì)列連接(Queued Connection)
當(dāng)控制權(quán)回到接受者所在線程的事件循環(huán)式,槽函數(shù)被調(diào)用。
槽函數(shù)在接收者所在線程執(zhí)行。
同前面一樣,這些文字大家都能看懂。但含義呢?
不妨繼續(xù)拿前面的例子來(lái)看,slot 函數(shù)是在主線程還是次線程中執(zhí)行呢?
定理二強(qiáng)調(diào)兩個(gè)概念:發(fā)送者所在線程 和 接收者所在線程。而 slot 函數(shù)屬于我們?cè)趍ain中創(chuàng)建的對(duì)象 thread,即thread屬于主線程
隊(duì)列連接告訴我們:槽函數(shù)在接受者所在線程執(zhí)行。即 slot 將在主線程執(zhí)行
直接連接告訴我們:槽函數(shù)在發(fā)送者所在線程執(zhí)行。發(fā)送者在那個(gè)線程呢??不定!
自動(dòng)連接告訴我們:二者不在同一線程時(shí),等同于隊(duì)列連接。即 slot 在主線程執(zhí)行
要徹底理解這幾句話,你可能需要看Qt meta-object系統(tǒng)和Qt event系統(tǒng))
如果上兩節(jié)看不懂,就記住下面的話吧(自己總結(jié)的,用詞上估計(jì)會(huì)不太準(zhǔn)確)。
QThread 是用來(lái)管理線程的,它所處的線程和它管理的線程并不是同一個(gè)東西
QThread 所處的線程,就是執(zhí)行 QThread t(0) 或 QThread * t=new QThread(0) 的線程。也就是咱們這兒的主線程
QThread 管理的線程,就是 run 啟動(dòng)的線程。也就是次線程
因?yàn)镼Thread的對(duì)象在主線程中,所以他的slot函數(shù)會(huì)在主線程中執(zhí)行,而不是次線程。除非:QThread 對(duì)象在次線程中
slot 和信號(hào)是直接連接,且信號(hào)所屬對(duì)象在次線程中
但上兩種解決方法都不好,因?yàn)镼Thread不是這么用的(Bradley T. Hughes)
好了,不在添加更多文字了,看代碼,估計(jì)咱們都會(huì)輕松點(diǎn)
主線程(信號(hào))QThread(槽)
這是Qt Manual 和 例子中普遍采用的方法。 但由于manual沒(méi)說(shuō)槽函數(shù)是在主線程執(zhí)行的,所以不少人都認(rèn)為它應(yīng)該是在次線程執(zhí)行了。
定義一個(gè) Dummy 類,用來(lái)發(fā)信號(hào)
定義一個(gè) Thread 類,用來(lái)接收信號(hào)
重載 run 函數(shù),目的是打印 threadid
- /*!
- * \file main.cpp
- *
- * Copyright (C) 2010, dbzhang800
- * All rights reserved.
- *
- */
- #include <QtCore/QCoreApplication>
- #include <QtCore/QObject>
- #include <QtCore/QThread>
- #include <QtCore/QDebug> 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(); }
然后看到結(jié)果(具體值每次都變,但結(jié)論不變)
- main thread: 0x1a40 from thread slot_main: 0x1a40 thread thread: 0x1a48
看到了吧,槽函數(shù)的線程和主線程是一樣的!
如果你看過(guò)Qt自帶的例子,你會(huì)發(fā)現(xiàn) QThread 中 slot 和 run 函數(shù)共同操作的對(duì)象,都會(huì)用QMutex鎖住。為什么?因?yàn)閟lot和run處于不同線程,需要線程間的同步!
如果想讓槽函數(shù)slot在次線程運(yùn)行(比如它執(zhí)行耗時(shí)的操作,會(huì)讓主線程死掉),怎么解決呢?
注意:發(fā)送dummy信號(hào)是在主線程, 接收者 thread 也在主線程中。 參考我們前面的結(jié)論,很容易想到: 將 thread 放到次線程中不就行了 這也是代碼中注釋掉的 moveToThread(this)所做的,去掉注釋,你會(huì)發(fā)現(xiàn)slot在次線程中運(yùn)行
- main thread: 0x13c0 thread thread: 0x1de0 from thread slot_main: 0x1de0
這可以工作,但這是 Bradley T. Hughes 強(qiáng)烈批判的用法。推薦的方法后面會(huì)給出。
#p#
run中信號(hào)與QThread中槽
定義一個(gè) Dummy 類,在run中發(fā)射它的信號(hào)
也可以在run中發(fā)射 Thread 中的信號(hào),而不是Dummy(效果完全一樣),QThread 定義槽函數(shù),重載run函數(shù)
- /*!
- * \file main.cpp
- *
- * Copyright (C) 2010, dbzhang800
- * All rights reserved.
- *
- */
- #include <QtCore/QCoreApplication>
- #include <QtCore/QObject>
- #include <QtCore/QThread>
- #include <QtCore/QDebug>
- class Dummy:public QObject {
- Q_OBJECT public: Dummy(QObject* parent=0):QObject(parent){}
- public slots: oid 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(); e
- xec();
- }
- };
- #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(); }
想看結(jié)果么?
- main thread: 0x15c0 thread thread: 0x1750 from thread slot_thread: 0x15c0
其實(shí)沒(méi)懸念,肯定是主線程
thread 對(duì)象本身在主線程。所以它的槽也在要在主線程執(zhí)行,如何解決呢?
(方法一)前面提了 moveToThread,這兒可以用,而且可以解決問(wèn)題。當(dāng)同樣,是被批判的對(duì)象。
(方法二)注意哦,這兒我們的信號(hào)時(shí)次線程發(fā)出的,對(duì)比connect連接方式,會(huì)發(fā)現(xiàn):
采用直接連接,槽函數(shù)將在次線程(信號(hào)發(fā)出的線程)執(zhí)行
這個(gè)方法不太好,因?yàn)槟阈枰幚韘lot和它的對(duì)象所在線程的同步。需要 QMutex 一類的東西
推薦的方法,其實(shí),這個(gè)方法太簡(jiǎn)單,太好用了。定義一個(gè)普通的QObject派生類,然后將其對(duì)象move到QThread中。使用信號(hào)和槽時(shí)根本不用考慮多線程的存在。也不用使用QMutex來(lái)進(jìn)行同步,Qt的事件循環(huán)會(huì)自己自動(dòng)處理好這個(gè)。
- /*!
- * \file main.cpp
- *
- * Copyright (C) 2010, dbzhang800
- * All rights reserved.
- *
- */
- #include <QtCore/QCoreApplication>
- #include <QtCore/QObject>
- #include <QtCore/QThread>
- #include <QtCore/QDebug>
- class Dummy:public QObject {
- Q_OBJECT
- public: Dummy(QObject* parent=0):QObject(parent) {}
- public slots: void emitsig() { emit sig();
- } signals: void sig(); };
- class Object:public QObject {
- Q_OBJECT
- public: Object(){} public slots:void slot() {
- qDebug()<<"from thread slot:" <<QThread::currentThreadId();
- }
- };
- #include "main.moc" int main(int argc, char *argv[]) {
- QCoreApplication a(argc, argv);
- qDebug()<<"main thread:"<<QThread::currentThreadId();
- QThread thread;
- Object obj;
- Dummy dummy;
- obj.moveToThread(&thread);
- QObject::connect(&dummy, SIGNAL(sig()), &obj, SLOT(slot()));
- thread.start();
- dummy.emitsig();
- return a.exec(); }
結(jié)果:恩,slot確實(shí)不在主線程中運(yùn)行(這么簡(jiǎn)單不值得歡呼么?)
- main thread: 0x1a5c from thread slot: 0x186c
小結(jié):Qt中QThread使用方法,講到這里。在本文中多次提到線程,那么對(duì)于QThread類,它提供了與系統(tǒng)無(wú)關(guān)的線程。QThread代表在程序中一個(gè)單獨(dú)的線程控制,在多任務(wù)操作系統(tǒng)中,它和同一進(jìn)程中的其它線程共享數(shù)據(jù),但運(yùn)行起來(lái)就像一個(gè)單獨(dú)的程序一樣。它不是在main()中開(kāi)始,QThread是在run()中開(kāi)始運(yùn)行的。
【編輯推薦】
解析 QT 靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)
Qt中實(shí)現(xiàn)QThread線程同步QFtp