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