Qt執行緒與事件迴圈的正確用法
週末天冷,索性把電腦抱到床上上網,這幾天看了 dbzhang800 部落格關於 Qt 事件迴圈的幾篇 Blog,發現自己對 Qt 的事件迴圈有不少誤解。從來只看到現象,這次借 dbzhang800 的部落格,就程式碼論事,因此瞭解到一些 Qt 深層的實現,雖然是在 Qt 龐大的構架裡只算的是冰山的一角,確讓人頗為收益。
從 dbzhang800 的部落格中轉載兩篇關於事件迴圈的文章,放在一起,寫作備忘。
再次提到的一點是:事件迴圈和執行緒沒有必然關係。 QThread 的 run() 方法始終是在一個單獨執行緒執行的,但只有在 run() 方法中使用了 exec() 才真正開啟了一個單獨的事件迴圈。
順便一提,模態對話方塊和事件迴圈也沒有必然關係,可以轉讀這篇文章 QDialog 模態對話方塊與事件迴圈。
以下內容轉載自 dbzhang800 的部落格:
「QThread 的使用方法」
原文地址:http://hi.baidu.com/cyclone/blog/item/5fac3bc7ab1b90d1d10060f2.html
「QThread 使用探討」
原文地址:http://hi.baidu.com/cyclone/blog/item/a33794ee00acba262cf53442.html
QThread 的使用方法
起源
昨天不小心看到Qt開發人員( Bradley T. Hughes)Blog中的一片文章 you are-doing-it-wrong
莫非manual、exmaple以及資料中的介紹都是錯的??
認真看看其他的人的評論,總算理清了一點頭緒。所有事情源於 QThread 的事件迴圈!
QThread 的兩種使用方法
1. 不使用事件迴圈。這是官方的 Manual 、example 以及相關書籍中都介紹的一種的方法。
a. 子類化 QThread
b. 過載 run 函式,run函式內有一個 while 或 for 的死迴圈
c. 設定一個標記為來控制死迴圈的退出。
a. 子類化 QThread,
b. 過載 run 使其呼叫 QThread::exec()
c. 併為該類定義訊號和槽,這樣一來,由於槽函式並不會在新開的 thread 執行,很多人為了解決這個問題在建構函式中呼叫
moveToThread(this)
而爭論和不解正是這樣的一條語句造成的。
Bradley T. Hughes 給出說明是: QThread 應該被看做是作業系統執行緒的介面或控制點,而不應該包含需要在新執行緒中執行的程式碼。需要執行的程式碼應該放到一個QObject的子類中,然後將該子類的物件moveToThread到新執行緒中。
另外
在Qt4.3(包括)之前,run 是虛擬函式,必須子類化QThread來實現run函式。
而從Qt4.4開始,qthreads-no-longer-abstract ,run 預設呼叫 QThread::exec() 。這樣一來不需要子類化 QThread 了,只需要子類化一個 QObject 就夠了,這正是被 Bradley T. Hughes推薦的方法。
終於看懂了,但不管怎麼說,都應該是 QThread 當初的設計導致的這種問題,而所有文件和例子中都沒有提到該如何使用Qthread 進一步加劇了對QThread的這種誤用。
QThread 使用探討
QThread 似乎是很難的一個東西,特別是訊號和槽,有非常多的人(儘管使用者本人往往不知道)在用不恰當(甚至錯誤)的方式在使用 QThread,隨便用google一搜,就能搜出大量結果出來。無怪乎Qt的開發人員 Bradley T. Hughes 聲嘶力竭地喊you are-doing-it-wrong。
和眾多使用者一樣,初次看到這個時,感到 Bradley T. Hughes有 些莫名奇妙,小題大作。儘管不舒服,當時還是整理過一篇部落格QThread 的使用方法
時間過去3個月,儘管依然沒怎麼用thread;但今天csdn論壇中有人問到這個問題,想想還是盡我所能整理一下吧。提升自己,方便他人,何樂而不為呢?
QThread東西還是比較多的,而且我對底層物件瞭解有限,僅就一點進行展開(或許是大家最關心的一點):QThread中的slots在那個執行緒中執行?
QThread::run
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.
這麼短的文字一眼就看完了,可是,這是什麼意思呢?又能說明什麼問題呢?看段簡單程式碼:
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;
...
}
對照前面的定理,run函式中的程式碼時確定無疑要在次執行緒中執行的,那麼其他的呢?比如 slot 是在次執行緒還是主執行緒中執行?
你想說主執行緒,但又心有不甘,對麼?
QObject::connect
涉及訊號槽,我們就躲不過 connect 函式,只是這個函式大家太熟悉。我不好意思再用一堆廢話來描述它,但不說又不行,那麼折中一下,只看它的最後一個引數吧(為了簡單起見,只看它最常用的3個值)
下面的列表,我們暫稱為定理二:
- 自動連線(Auto Connection)
- 這是預設設定
- 如果訊號在接收者所依附的執行緒內發射,則等同於直接連線
- 如果發射訊號的執行緒和接受者所依附的執行緒不同,則等同於佇列連線
- 也就是這說,只存在下面兩種情況
- 直接連線(Direct Connection)
- 當訊號發射時,槽函式將直接被呼叫。
- 無論槽函式所屬物件在哪個執行緒,槽函式都在發射訊號的執行緒內執行。
- 佇列連線(Queued Connection)
- 當控制權回到接受者所依附執行緒的事件迴圈時,槽函式被呼叫。
- 槽函式在接收者所依附執行緒執行。
同前面一樣,這些文字大家都能看懂。但含義呢?
不妨繼續拿前面的例子來看,slot 函式是在主執行緒還是次執行緒中執行呢?
定理二強調兩個概念:傳送訊號的執行緒 和 接收者所依附的執行緒。而 slot 函式屬於我們在main中建立的物件 thread,即thread依附於主執行緒
- 佇列連線告訴我們:槽函式在接受者所依附執行緒執行。即 slot 將在主執行緒執行
- 直接連線告訴我們:槽函式在傳送訊號的執行緒執行。訊號在那個執行緒傳送呢??不定!
- 自動連線告訴我們:二者不同,等同於佇列連線。即 slot 在主執行緒執行
太繞了?不是麼(要徹底理解這幾句話,你可能需要看Qt meta-object系統和Qt event系統)
怎麼辦呢?
如果上兩節看不懂,就記住下面的話吧(自己總結的,用詞上估計會不太準確)。
- QThread 是用來管理執行緒的,它所依附的執行緒和它管理的執行緒並不是同一個東西
- QThread 所依附的執行緒,就是執行 QThread t(0) 或 QThread * t=new QThread(0) 的執行緒。也就是咱們這兒的主執行緒
- QThread 管理的執行緒,就是 run 啟動的執行緒。也就是次執行緒
- 因為QThread的物件依附在主執行緒中,所以他的slot函式會在主執行緒中執行,而不是次執行緒。除非:
- QThread 物件依附到次執行緒中(通過movetoThread)
- slot 和訊號是直接連線,且訊號在次執行緒中發射
- 但上兩種解決方法都不好,因為QThread不是這麼用的(Bradley T. Hughes)
好了,不再新增更多文字了,看程式碼,估計咱們都會輕鬆點
主執行緒(訊號)QThread(槽)
這是 Qt Manual 和 例子中普遍採用的方法。 但由於manual沒說槽函式是在主執行緒執行的,所以不少人都認為它應該是在次執行緒執行了。
- 定義一個 Dummy 類,用來發訊號
- 定義一個 Thread 類,用來接收訊號
- 過載 run 函式,目的是列印 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();
}
然後看到結果(具體值每次都變,但結論不變)
main thread: 0x1a40 from thread slot_main: 0x1a40 thread thread: 0x1a48
看到了吧,槽函式的執行緒和主執行緒是一樣的!
如果你看過Qt自帶的例子,你會發現 QThread 中 slot 和 run 函式共同操作的物件,都會用QMutex鎖住。為什麼?
因為slot和run處於不同執行緒,需要執行緒間的同步!
如果想讓槽函式slot在次執行緒執行(比如它執行耗時的操作,會讓主執行緒死掉),怎麼解決呢?
- 注意:dummy訊號是在主執行緒發射的, 接收者 thread 也在主執行緒中。
- 參考我們前面的結論,很容易想到:
- 將 thread 依附的執行緒改為次執行緒不就行了?
- 這也是程式碼中註釋掉的 moveToThread(this)所做的,去掉註釋,你會發現slot在次執行緒中執行
main thread: 0x13c0
thread thread: 0x1de0
from thread slot_main: 0x1de0
這可以工作,但這是 Bradley T. Hughes 強烈批判的用法。推薦的方法後面會給出。
run中訊號與QThread中槽
- 定義一個 Dummy 類,在run中發射它的訊號
- 也可以在run中發射 Thread 類中的訊號,而不是Dummy(效果完全一樣)
- QThread 定義槽函式,過載run函式
/*!
* \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 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();
}
想看結果麼?
main thread: 0x15c0
thread thread: 0x1750
from thread slot_thread: 0x15c0
- 其實沒懸念,肯定是主執行緒
- thread 物件本身在主執行緒。所以它的槽也在要在主執行緒執行
如何解決呢?
- (方法一)前面提了 moveToThread,這兒可以用,而且可以解決問題。當同樣,是被批判的物件。
- (方法二)注意哦,這兒我們的訊號時次執行緒發出的,對比connect連線方式,會發現:
- 採用直接連線,槽函式將在次執行緒(訊號發出的執行緒)執行
- 這個方法不太好,因為你需要處理slot和它的物件所線上程的同步。需要 QMutex 一類的東西
推薦的方法
千呼萬喚始出來。
其實,這個方法太簡單,太好用了。定義一個普通的QObject派生類,然後將其物件move到QThread中。使用訊號和槽時根本不用考慮多執行緒的存在。也不用使用QMutex來進行同步,Qt的事件迴圈會自己自動處理好這個。
/*!
* \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();
}
結果:恩,slot確實不在主執行緒中執行(這麼簡單不值得歡呼麼?)
main thread: 0x1a5c
from thread slot: 0x186c
其他
- 本文只考慮了使用事件迴圈的情況,也有可能run中沒有事件迴圈。這時訊號與槽會與本文有點差別。比如run中使用connect時,佇列連線就受限制了。其實只要理解了前面這些,沒有事件迴圈的情況很容易就想通了。
參看
- http://labs.qt.nokia.com/blogs/2010/06/17/youre-doing-it-wrong/
- http://labs.qt.nokia.com/blogs/2010/06/17/youre-doing-it-wrong/
- http://labs.qt.nokia.com/blogs/2006/12/04/threading-without-the-headache/
- http://labs.qt.nokia.com/blogs/2007/07/05/qthreads-no-longer-abstract/
- http://gitorious.org/qthreadhowto/qthreadhowto/trees/master
- http://blog.exys.org/entries/2010/QThread_affinity.html
- http://thesmithfam.org/blog/2010/02/07/talking-to-qt-threads/
- http://doc.qt.nokia.com/4.7/threads-starting.html
- http://doc.qt.nokia.com/4.7/threads-qobject.html
- http://doc.qt.nokia.com/4.7/qthread.html
相關推薦
Qt執行緒與事件迴圈的正確用法
週末天冷,索性把電腦抱到床上上網,這幾天看了 dbzhang800 部落格關於 Qt 事件迴圈的幾篇 Blog,發現自己對 Qt 的事件迴圈有不少誤解。從來只看到現象,這次借 dbzhang800 的部落格,就程式碼論事,因此瞭解到一些 Qt 深層的實現,雖然是在 Qt
【Qt:語法】Qt 的執行緒與事件迴圈
週末天冷,索性把電腦抱到床上上網,這幾天看了 dbzhang800 部落格關於 Qt 事件迴圈的幾篇 Blog,發現自己對 Qt 的事件迴圈有不少誤解。從來只看到現象,這次借 dbzhang800 的部落格,就程式碼論事,因此瞭解到一些 Qt 深層的實現,
(轉載)Qt 的執行緒與事件迴圈——可列印threadid進行觀察槽函式到底是在哪個執行緒裡執行,學習moveToThread的使用)
從 dbzhang800 的部落格中轉載兩篇關於事件迴圈的文章,放在一起,寫作備忘。 再次提到的一點是:事件迴圈和執行緒沒有必然關係。 QThread 的 run() 方法始終是在一個單獨執行緒執行的,但只有在 run() 方法中使用了 exec() 才真正開啟了一個單獨的事
單執行緒與事件迴圈
單執行緒指的是主執行緒是“單執行緒”的,所有阻塞的部分交給一個執行緒池處理,然後這個主執行緒通過一個佇列跟執行緒池協作,於是對我們寫到的js程式碼部分,不再關心執行緒問題,程式碼也主要由一堆callback回撥構成,然後主執行緒在不停的迴圈過程中,適時呼叫這些程式碼。 什麼是 Event Loo
JS-執行緒、事件迴圈、任務佇列
JS 是單執行緒的,但是卻能執行非同步任務,這主要是因為 JS 中存在事件迴圈(Event Loop)和任務佇列(Task Queue)。 事件迴圈: JS 會建立一個類似於 while (true) 的迴圈,每執行一次迴圈體的過程稱之為 Tick。每次 Tick 的過程就是檢視是否有待處理事件,如果有則
單執行緒、事件迴圈和非同步IO
單執行緒 Node.js本身是多執行緒的,只是在解釋我們編寫的javascript程式碼時是由一個執行緒完成的,而底層是採用了多執行緒的方式來處理各種I/O操作的。 事件迴圈 在Node.js啟動動時, 會建立一個類似於while(true)
效能特性測試系列4——QT執行緒與std::thread(下)之QThread
其實用法早就總結了,但是因為自身事務原因,一直沒有比較他們間的效率,甚至各種互斥量,條件變數之間的效率,更多是集中看了下他們各自的api和特點,所以估計以後還需要補充或者是另開一篇來講= =。 序 QThread的執行緒用法上與std::thr
QTimer與事件迴圈和多執行緒
定時器的原始碼分析 startTimer返回定時器的ID,在定時時間到了後,收到一個QTimerEvent,並覆蓋虛擬函式timerEvent進行處理,該QTimerEvent包括了定時器ID 看QTimer的原始碼就明白了: QObject::sta
Qt 筆記:多執行緒與介面元件的通訊(上)
是否可以在子執行緒中建立介面元件? class TestThread : public QThread { Q_OBJECT protected: void run() { QWidget w; w.show();
Qt:筆記:多執行緒與介面元件的通訊(下)
子執行緒能夠更改介面元件狀態的本質是什麼? -子執行緒發射訊號通知主執行緒介面更新請求;主執行緒根據具體訊號遺蹟訊號引數對介面元件進行修改。 是否有其他間接的方式可以讓子執行緒更新介面元件的狀態? 解決方案 - 傳送自定義事件 -自定義事件類用於描述介面更新細節 -在主視窗類中重
Java執行緒與執行緒安全,開啟多執行緒及每執行緒迴圈10次對類進行輸出測試
最近看到執行緒問題,emmm~腦闊回想到計算機作業系統貌似又講,不過上課睡覺覺去啦哈哈哈,java課老師莫得講~ 然歸正傳,今對執行緒進行查閱及測試,做一下筆記,有錯之處還請指出,謝謝~上程式碼之前呢先說一哈前傳 執行緒是程序中的最小執行單位: 手機呢會有很多單獨
對qt多執行緒以及事件投遞的理解
用QThread啟動執行緒的良好方法以及理解。 1) 用原始的QThread的started訊號觸發自定義的slot啟動執行緒,而不是派生QThread的類過載run函式啟動執行緒。 將一個類派生自QObject,然後實現所有的signal/slot,然後通過呼叫movetothread函式來使他們執行在
執行緒與互斥鎖(C++11中std::thread和std::mutex的用法)
執行緒 0 首先是曾經在MultiCMOS專案中用到的: #include <thread> //包含標頭檔案 class IDataProcessUnit { protected:
QT執行緒池實驗研究與分析(QThread與和QThreadPool + QRunnable使用上的區別)
第一部分:QT執行緒池的構建與使用 網上關於QT執行緒池QThreadPool的文章很多,而且大都千篇一律,基本上都是參考QT的幫助文件介紹QT全域性執行緒池的用法。這樣就往往會使人產生誤解,QT是不是推薦大家使用其全域性執行緒池,而不推薦使用自定義構造的執行緒池?
Qt 多執行緒程式設計中的物件執行緒與函式執行執行緒
為徹底搞清原因並解決問題,在查閱大量資料和Qt文件之後,理清了其中的機制,也對多執行緒程式設計中的QObject物件建立以及connect執行有更清楚的認識: 1. 一個物件的執行緒就是建立該物件時的執行緒,而不論該物件的定義是儲存在那個執行緒中; 2. QObject的connec
Qt中QThread執行緒與主執行緒的資料傳送以及lineEdit的顯示
最近因為專案的需要,需要qt與C語言的程序間通訊,這裡表示Qt為A程序,C語言為B程序。 這裡採用管道通訊。 A程序的執行緒讀取管道,B程序寫入管道。 A執行緒讀到char 字元陣列中,然後再主執行緒中lineEdit顯示。 遇到一個char 和QString的相互轉換 這
QT GUI(主)執行緒與子執行緒之間的通訊——使用跨執行緒的訊號槽
在主執行緒上,可以控制子執行緒啟動,停止,清零 如果子執行緒啟動的話,每一秒鐘會向主執行緒傳送一個數字,讓主執行緒更新介面上的數字。 程式截圖: 上程式碼: #include <QtGui> #include <QtCore> #include <w
QT子執行緒與主執行緒的訊號槽通訊
最近用QT做一個伺服器,眾所周知,QT的主執行緒必須保持暢通,才能重新整理UI。所以,網路通訊端採用新開執行緒的方式。在涉及到使用子執行緒更新Ui上的控制元件時遇到了點兒麻煩。網上提供了很多同一執行緒不同類間採用訊號槽通訊的方式,但是並不完全適合執行緒間的訊號槽
QT小例子GUI(主)執行緒與子執行緒之間的通訊
在主執行緒上,可以控制子執行緒啟動,停止,清零 如果子執行緒啟動的話,每一秒鐘會向主執行緒傳送一個數字,讓主執行緒更新介面上的數字。 #ifndef TQT_H_ #define TQT_H_ #include <QtGui> #include &l
Qt 多執行緒與資料庫操作需要注意的幾點問題
相關文章徹底拋棄MFC, 全面應用Qt 已經不少時間了。除了自己看書按步就班做了十幾個驗證性的應用,還正式做了3個比較大的行業應用,總體感覺很好。Native C++ 下, Qt 基本是我用過的最簡便的介面庫了。遇到了一些問題,大都解決的很順利,回頭想想,還是有幾個問題很有意