Qt-Threads和QObjects详解
簡(jiǎn)述
QThread繼承自QObject,它發(fā)射信號(hào)(signals)以表明線程執(zhí)行開始或結(jié)束,并提供了一些槽函數(shù)(slots)。
更有趣的是,QObjects可以在多線程中使用,發(fā)射信號(hào)以在其它線程中調(diào)用槽函數(shù),并且向“存活”于其它線程中的對(duì)象發(fā)送事件(post events)。這是可能的,因?yàn)槊恳粋€(gè)線程都擁有它自身的事件循環(huán)(event loop)。
- 簡(jiǎn)述
- QObject可重入性
- 每個(gè)線程的事件循環(huán)
- 從其它線程訪問QObject子類
- 跨線程的信號(hào)和槽
QObject可重入性
QObject是可重入的。它的大多數(shù)非GUI子類,例如:QTimer、QTcpSocket、QUdpSocket和QProcess,也都是可重入的,這使得在多線程中同時(shí)使用這些類成為可能。注意:這些類被設(shè)計(jì)成在單一線程中創(chuàng)建和使用的,在一個(gè)線程中創(chuàng)建一個(gè)對(duì)象而在另一個(gè)線程中調(diào)用該對(duì)象的函數(shù),不保證能行得通。需要注意有三個(gè)約束:
-
一個(gè)QObject類型的孩子必須總是被創(chuàng)建在它的父親所被創(chuàng)建的線程中。這意味著,除了別的以外,永遠(yuǎn)不要把QThread對(duì)象(this)作為該線程中創(chuàng)建的一個(gè)對(duì)象的父親(因?yàn)镼Thread對(duì)象自身被創(chuàng)建在另外一個(gè)線程中)。
-
事件驅(qū)動(dòng)的對(duì)象可能只能被用在一個(gè)單線程中。特別是,這適用于計(jì)時(shí)器機(jī)制(timer mechanism)和網(wǎng)絡(luò)模塊。例如:你不能在不屬于這個(gè)對(duì)象的線程中啟動(dòng)一個(gè)定時(shí)器或連接一個(gè)socket,必須保證在刪除QThread之前刪除所有創(chuàng)建在這個(gè)線程中的對(duì)象。在run()函數(shù)的實(shí)現(xiàn)中,通過在棧中創(chuàng)建這些對(duì)象,可以輕松地做到這一點(diǎn)。
-
雖然QObject是可重入的,但GUI類,尤其是QWidget及其所有子類都不是可重入的,它們只能被用在主線程中。如前面所述,QCoreApplication::exec()必須也從那個(gè)線程被調(diào)用。
在實(shí)踐中,只能在主線程而非其它線程中使用GUI的類這一點(diǎn),可以很輕易地被解決:將耗時(shí)操作放在一個(gè)單獨(dú)的worker線程中,當(dāng)worker線程結(jié)束后在主線程中由屏幕顯示結(jié)果。這個(gè)方法被用來實(shí)現(xiàn)Mandelbrot Example和the Blocking Fortune Client Example。
一般來說,在QApplication之前就創(chuàng)建QObject是不行的,會(huì)導(dǎo)致奇怪的崩潰或退出,取決于平臺(tái)。這意味著,也不支持QObject的靜態(tài)實(shí)例。一個(gè)單線程或多線程的應(yīng)用程序應(yīng)該先創(chuàng)建QApplication,并最后銷毀QObject。
每個(gè)線程的事件循環(huán)
每個(gè)線程都有它自己的事件循環(huán)。初始線程通過QCoreApplication::exec()來啟動(dòng)它的事件循環(huán), 或者對(duì)于單對(duì)話框的GUI應(yīng)用程序,有些時(shí)候用QDialog::exec(),其它線程可以用QThread::exec()來啟動(dòng)事件循環(huán)。就像QCoreApplication,QThread提供一個(gè)exit(int)函數(shù)和quit()槽函數(shù).
一個(gè)線程中的事件循環(huán)使得該線程可以利用一些非GUI的、要求有事件循環(huán)存在的Qt類(例如:QTimer、QTcpSocket、和QProcess)。它也使得連接一些線程的信號(hào)到一個(gè)特定線程的槽函數(shù)成為可能。這一點(diǎn)將會(huì)在下面的“跨線程的信號(hào)和槽”有詳細(xì)介紹。
一個(gè)QObject實(shí)例被稱為存活于它所被創(chuàng)建的線程中。關(guān)于這個(gè)對(duì)象的事件被分發(fā)到該線程的事件循環(huán)中。可以用QObject::thread()方法獲取一個(gè)QObject所處的線程。
QObject::moveToThread()函數(shù)改變一個(gè)對(duì)象和它的孩子的線程所屬性。(如果該對(duì)象有父親的話,它不能被移動(dòng)到其它線程中)。
從另一個(gè)線程(不是該QObject對(duì)象所屬的線程)對(duì)該QObject對(duì)象調(diào)用delete方法是不安全的,除非你能保證該對(duì)象在那個(gè)時(shí)刻不處理事件,使用QObejct::deleteLater()更好。一個(gè)DeferredDelete類型的事件將被提交(posted),而該對(duì)象的線程的事件循環(huán)最終會(huì)處理這個(gè)事件。默認(rèn)情況下,擁有一個(gè)QObject的線程就是創(chuàng)建QObject的那個(gè)線程,而不是QObject::moveToThread()被調(diào)用后的。
如果沒有事件循環(huán)運(yùn)行,事件將不會(huì)傳遞給對(duì)象。例如:你在一個(gè)線程中創(chuàng)建了一個(gè)QTimer對(duì)象,但從沒有調(diào)用exec(),那么,QTimer就永遠(yuǎn)不會(huì)發(fā)射timeout()信號(hào),即使調(diào)用deleteLater()也不行。(這些限制也同樣適用于主線程)。
利用線程安全的方法QCoreApplication::postEvent(),你可以在任何時(shí)刻給任何線程中的任何對(duì)象發(fā)送事件,這些事件將自動(dòng)被分發(fā)到該對(duì)象所被創(chuàng)建的線程事件循環(huán)中。
所有的線程都支持事件過濾器,而限制是監(jiān)控對(duì)象必須和被監(jiān)控對(duì)象存在于相同的線程中。類似的,QCoreApplication::sendEvent()(不同于postEvent())只能將事件分發(fā)到和該函數(shù)調(diào)用者相同的線程中的對(duì)象。
從其它線程訪問QObject子類
QObject及其所有子類都不是線程安全的。這包含了整個(gè)事件交付系統(tǒng)。重要的是,切記事件循環(huán)可能正在向你的QObject子類發(fā)送事件,當(dāng)你從另一個(gè)線程訪問該對(duì)象時(shí)。
如果你正在調(diào)用一個(gè)QObject子類的函數(shù),而該子類對(duì)象并不存活于當(dāng)前線程中,并且該對(duì)象是可以接收事件的,那么你必須用一個(gè)mutex保護(hù)對(duì)該QObject子類的內(nèi)部數(shù)據(jù)的所有訪問,否則,就有可能發(fā)生崩潰和非預(yù)期的行為。
同其它對(duì)象一樣,QThread對(duì)象存活于該對(duì)象被創(chuàng)建的線程中 – 而并非是在QThread::run()被調(diào)用時(shí)所在的線程。一般來說,在QThread子類中提供槽函數(shù)是不安全的,除非用一個(gè)mutex保護(hù)成員變量。
另一方面,可以在QThread::run()的實(shí)現(xiàn)中安全地發(fā)射信號(hào),因?yàn)樾盘?hào)發(fā)射是線程安全的。
跨線程的信號(hào)和槽
Qt支持如下的信號(hào)-槽連接類型:
-
Auto Connection(默認(rèn)):如果信號(hào)在接收者所依附的線程內(nèi)發(fā)射,則等同于Direct Connection。否則,等同于Queued Connection。
-
Direct Connection:當(dāng)信號(hào)發(fā)射后,槽函數(shù)立即被調(diào)用。槽函數(shù)在信號(hào)發(fā)射者所在的線程中執(zhí)行,而未必需要在接收者的線程中。
-
Queued Connection:當(dāng)控制權(quán)回到接受者所在線程的事件循環(huán)時(shí),槽函數(shù)被調(diào)用。槽函數(shù)在接收者的線程中執(zhí)行。
-
Blocking Queued Connection:槽函數(shù)的調(diào)用情形和Queued Connection相同,不同的是當(dāng)前的線程會(huì)阻塞住,直到槽函數(shù)返回。?
注意:在同一個(gè)線程中使用這種類型進(jìn)行連接會(huì)導(dǎo)致死鎖。 -
Unique Connection:行為與Auto Connection相同,但是連接只會(huì)在“不會(huì)與已存在的連接相同”時(shí)建立,也就是:如果相同的信號(hào)已經(jīng)被連接到相同的槽函數(shù),那么連接就不會(huì)被再次建立,并且connect()會(huì)返回false。
通過傳遞一個(gè)參數(shù)給connect()來指定連接類型。要知道,如果一個(gè)事件循環(huán)運(yùn)行在接收者的線程中,而發(fā)送者和接收者位于不同的線程時(shí),使用Direct Connection是不安全的。同樣的原因,調(diào)用存活于另一個(gè)線程中的對(duì)象的任何函數(shù)都是不安全的。
QObject::connect()本身是線程安全的。
Mandelbrot Example使用了Queued Connection來連接一個(gè)worker線程和主線程。為了避免凍結(jié)主線程的事件循環(huán)(即避免因此而凍結(jié)了應(yīng)用的UI),所有的曼德爾布羅特分形計(jì)算(Mandelbrot fractal computation)都是在一個(gè)單獨(dú)的worker線程中完成的,線程結(jié)束一個(gè)計(jì)算時(shí)發(fā)射一個(gè)信號(hào)。
同樣,Blocking Fortune Client Example使用了一個(gè)單獨(dú)的線程來和TCP server進(jìn)行異步通信。
?
總結(jié)
以上是生活随笔為你收集整理的Qt-Threads和QObjects详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vue 子组件 调用、触发父组件中的方法
- 下一篇: Qt 事件处理机制-qt源码解读