QThread從QObject派生。它發(fā)出信號(hào)來表明線程開始了或結(jié)束了。并且也提供了幾個(gè)槽。
更有趣的是,QObject可以在多個(gè)程中同時(shí)使用,可以發(fā)出信號(hào)給另外線程的槽,以及向“活在”另外線程中的對(duì)象郵寄事件。以上之所以能發(fā)生,是因?yàn)槊總€(gè)初程都被允許擁有它自己的事件循環(huán)。
QObject 重入
QObject 是可重入的。它大多數(shù)非界面派生類,比如QTimer,QTcpSocket,QFtp,和QProcess,也都是可重入的,使得在多個(gè)線程中同時(shí)使用 這些類成為可能。但是注意這些類被設(shè)計(jì)為在一個(gè)線程中創(chuàng)建和使用;在一個(gè)線程中創(chuàng)建一個(gè)對(duì)象然后在另一個(gè)線程中調(diào)用它的方法是不能保證一定能工作的。有三 個(gè)限制條件要注意:
QObject的兒子必須在創(chuàng)建它爹的線程中創(chuàng)建。這表示,你永遠(yuǎn)不能將QThread對(duì)象(this)作為parent傳給在此thread創(chuàng)建的對(duì)象,因?yàn)榫€程對(duì)象自己就是在另一個(gè)線程中創(chuàng)建的。
事件驅(qū)動(dòng)的對(duì)象應(yīng)該只用于一個(gè)線程中。這一條尤其應(yīng)用于定時(shí)器和網(wǎng)絡(luò)模塊。比如,你不能在創(chuàng)建對(duì)象之外的線程中啟動(dòng)一個(gè)定時(shí)器或連接一個(gè)socket。
你必須保證在線程中創(chuàng)建的一切對(duì)象在QThread被刪除之前被刪除。這可以通過在你的run()實(shí)現(xiàn)中在棧中創(chuàng)建對(duì)象來輕松搞定。
盡管QObject是可重入的,但GUI類,尤其是QWidget和它所有的派生類們,都不是可重入的。它們只能在主線程中使用。QCoreApplication::exec()必須在這個(gè)線程中調(diào)用。
在實(shí)際應(yīng)用中,最好的方式是把耗時(shí)的計(jì)算放到主線程中外進(jìn)行,完成后通知主線程顯示結(jié)果。
Pre-Thread Event循環(huán)
每 個(gè)線程都可以有它自己的事件循環(huán)。初始的線程使用QCoreApplication::exec()來開始它的事件循環(huán);其它的線程可以使用 QThread::exec()來啟動(dòng)循環(huán)。就像QCoreApplication,QThread也提供了一個(gè)exit(int)方法和一個(gè) quit()槽。
線程中的事件循環(huán)使得在線程中使用依靠消息循環(huán)的非GUI的QT類成為可能(比如QTimer,QTcpSocket,QProcess)。它也使得從任何線程連接信號(hào)到一個(gè)線程的槽成為可能。這在下面的“信號(hào)和槽穿越線程”一節(jié)中有詳細(xì)解釋。
一個(gè)QObject實(shí)例在那個(gè)線程中創(chuàng)建,就叫做“活”在那個(gè)線程中。給這個(gè)對(duì)象的事件們通過線程的事件循環(huán)派發(fā)。一個(gè)QObject對(duì)象所“活在”的線程通過QObject::thread()可以取得。
注 意在QApplication之前創(chuàng)建的QObject調(diào)用QObject::thread()會(huì)返回0.這意味著主線程將只為這些對(duì)象處理郵寄的事件; 對(duì)于沒有線程的對(duì)象,其它的事件處理跟本不會(huì)發(fā)生。使用QObject::moveToThread()方法來改變對(duì)象(和它兒子們)的線程(如果一個(gè)對(duì) 象有爹,它就不能被移動(dòng)到另外線程)。
在擁有對(duì)象之外的線程中調(diào)用刪除對(duì)象是不安全的,除非你能保證在被刪除時(shí)不在處理事件。但可以使用 QObject::deleteLater(),它會(huì)寄出DeferedDelete事件,對(duì)象的線程的事件循環(huán)最終會(huì)抓住它。默認(rèn)下,擁有 Qobject的線程就是創(chuàng)建QObject的線程,但在QObject::moveToTread()之后就變了。
如果沒有事件循環(huán),事件將不能傳給對(duì)象。比如,如果你在一個(gè)線程中創(chuàng)建一個(gè)QTimer對(duì)象,但是沒有再調(diào)用exec(),那么QTimer將永不能觸發(fā)timeout()信號(hào)。deleteLater()也不再能工作。(這些也同樣適用于主線程。)
你可以在任何線程中使用QCoreApp:postEvent()手動(dòng)向任何對(duì)象郵寄事件。事件將被對(duì)象所在線程的事件循環(huán)自動(dòng)派發(fā)。
事 件過濾器被所有的線程所支持,但有個(gè)限制條件:監(jiān)視對(duì)象必須與被監(jiān)視對(duì)象位于同一個(gè)線程中。同樣 的,QCoreApplication::sendEvent()(不同于 QCoreApplication::postEvent())只能在同一線程中的對(duì)象之間發(fā)送事件。
從另外線程訪問QObject子類
QObject和它所有的子類都不是線程安全的,這也包含整個(gè)事件派送系統(tǒng)。要記住,當(dāng)你從另外線程訪問對(duì)象時(shí),事件循環(huán)可能派送事件到你的QObject子類。
如果你調(diào)用一個(gè)非本線程的QObject的子類的函數(shù)并且這個(gè)對(duì)象可能接收事件,你必須用mutex保護(hù)所有對(duì)你的QObject子類的內(nèi)部數(shù)據(jù)的訪問;否則,你可能體驗(yàn)的什么叫崩潰。
就像其它對(duì)象,QThread對(duì)象“活”在創(chuàng)建它的線程中,而不是它自己所代表的線程中。通常在你的QThread子類中提供槽是不安全的,除非你用mutex保護(hù)成員變量。
另一方面,你可以從你的QThread tun()中安全的發(fā)出信號(hào),因?yàn)樾盘?hào)發(fā)射是線程安全的。
穿越線程的信號(hào)和槽們
Qt支持如下信號(hào)-槽連接類型:
自動(dòng)連接(默認(rèn))- 如果信號(hào)是從接收對(duì)象所在的線程發(fā)出的,其行為與“直接連接”相同。否則,其行為與“隊(duì)列連接”相同。
直接連接- 當(dāng)信號(hào)發(fā)出,槽會(huì)被立馬調(diào)用。此槽在發(fā)出者的線程中執(zhí)行,而不一定是接收者所在的線程。
隊(duì)列連接- 當(dāng)控制返回到接收者所在線程的事件循環(huán)時(shí)調(diào)用。槽在接收者的線程中執(zhí)行。
阻塞的隊(duì)列連接- 槽像“隊(duì)列連接”那樣被調(diào)用,除了一點(diǎn):當(dāng)前線程會(huì)阻塞住直到槽返回。注:在同一線程中使用此類型的連接將導(dǎo)致死鎖!
唯一連接- 行為與“自動(dòng)連接”相同,但連接必須在無復(fù)制品時(shí)才能建立。也就是,如果在相同的兩個(gè)對(duì)象之間已經(jīng)建立了同一個(gè)信號(hào)到同一個(gè)槽的連接,那么連接就不能建立,connect()返回false。
連接類型可以通過給connect()傳遞一個(gè)額外的參數(shù)來指定。注意當(dāng)接收者和發(fā)送者位于不同的線程中時(shí),使用“直接連接”,如果事件循環(huán)是運(yùn)行于接收者的線程中,此時(shí)是不安全的,同理調(diào)用位于另外線程的對(duì)象的任何函數(shù)都是不安全的。
QObject::connect()本身是線程安全的。