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