Qt信號槽的一些事(第一次知道信號還有返回值,以及Qt::UniqueConnection)


注:此文是站在Qt5的角度說的,對於Qt4部分是不適用的。

1.先說Qt信號槽的幾種連接方式和執行方式。

1)Qt信號槽給出了五種連接方式:

Qt::AutoConnection 0 自動連接:默認的方式。信號發出的線程和糟的對象在一個線程的時候相當於:DirectConnection, 如果是在不同線程,則相當於QueuedConnection
Qt::DirectConnection 1 直接連接:相當於直接調用槽函數,但是當信號發出的線程和槽的對象不再一個線程的時候,則槽函數是在發出的信號中執行的。
Qt::QueuedConnection 2 隊列連接:內部通過postEvent實現的。不是實時調用的,槽函數永遠在槽函數對象所在的線程中執行。如果信號參數是引用類型,則會另外復制一份的。線程安全的。
Qt::BlockingQueuedConnection 3 阻塞連接:此連接方式只能用於信號發出的線程(一般是先好對象的線程) 和 槽函數的對象不再一個線程中才能用。通過信號量+postEvent實現的。不是實時調用的,槽函數永遠在槽函數對象所在的線程中執行。但是發出信號后,當前線程會阻塞,等待槽函數執行完畢后才繼續執行。
Qt::UniqueConnection 0x80 防止重復連接。如果當前信號和槽已經連接過了,就不再連接了。

2)信號槽的調用方式和線程:

UniqueConnection 模式:嚴格說不算連接方式,方式就是4中,此只是一個附加的參數。不討論。

AutoConnection 模式:這個模式是默認的,但其可以看作是DirectConnection和QueuedConnection的自動選擇,直接分析那兩種也就行了。

發出信號,調用槽的方式也可以簡單的分為兩種:同步調用和異步調用

同步調用:發出信號后,當前線程等待槽函數執行完畢后才繼續執行。

異步調用:發出信號后,立即執行剩下邏輯,不關心槽函數什么時候執行。

所以有下表:

線程/模式 DirectConnection QueuedConnection BlockingQueuedConnection
相同線程 直接調用,同步調用。 通過事件進行隊列調用。異步調用. 不可用
不同線程 直接調用。同步調用。槽函數在發出信號的線程執行。有線程安全隱患。 通過事件進行隊列調用。異步調用.槽函數在對象所在的線程執行。線程安全。 通過事件進行阻塞調用。同步調用。槽函數在對象所在的線程執行。線程安全。
Qt事件循環依賴 直接調用,不依賴Qt事件循環 通過事件進行隊列調用。依賴,槽函數所在對象的線程必須啟用Qt事件循環 通過事件進行隊列調用,用信號量實現阻塞。依賴,槽函數所在對象的線程必須啟用Qt事件循環

 2.Qt信號連接多個槽,調用順序。

先說基本原則:

槽函數開始調用的順序和連接的順序是一致的。

但是,上面也說了,有同步調用和異步調用。

對於同步調用,你觀察的結果和基本原則一樣。

但是對於異步調用,可能你最先連接的它,但是可能其他都執行完畢了,但是其還沒執行。是因為對於異步調用:是開始調用的時候,生成一個需要調用這個函數的事件,然后放到事件隊列里。然后立即返回,去執行調用其他槽函數或者槽函數都執行了,不關心槽函數的執行狀態的。等到事件隊列里任務輪到此事件再去調用。

3.信號的返回值。

大都說Qt信號槽不能使用返回值。其實不不准確的,Qt5中,信號槽是有返回值的。只是Qt的一個信號可以連接多個槽,還有同步調用和異步調用的問題,沒發支持的很好,所以,返回值雖有,但只是雞肋。

先說下返回值的規則把:

  • 同步調用才有返回值,異步調用的返回值永遠為返回值類型默認構造函數出來的。
  • 連接的多個槽都返回值,那么結果是最后調用(連接)的那個。

也就是說對於QueuedConnection連接的信號槽,永遠只是返回返回類型的默認構造函數的。對於AutoConnection連接的,如果發出信號的線程和槽函數線程不同亦然。

測試小例子地址:https://github.com/dushibaiyu/DsbyLiteExample/tree/master/QtSignalsSlotTest

4.信號參數的安全問題:

因為一個信號可以連接多個槽函數,如果參數是T * 或者是T &話會不會第一個槽函數改變參數的值,然后第二此調用的參數就已經不是信號發出的值?

1)對於T &: 在同步調用中則是變化的,不可用於異步,不可跨線程。所以BlockingQueuedConnection方式的同步也不行。(T& 不可用在隊列調用(QueuedConnection)和阻塞調用(BlockingQueuedConnection)中。只能使用const T &。)

因為同步調用,你可以理解成直接調用,那么連接多個槽函數就相當於直接連續調用多個函數。類似於:

// 函數原型都是:void  (int &a )
int a;
    fun1(a);
    fun2(a)
    ·····
// 函數原型都是:void  (int * a )
        int a;
        pfun1(&a);
        pfun2(&a)
    ·····

 

這樣,當第一個函數執行改變參數值之后,其后的函數調用都要受影響。

2) 對於T *,最好不要同時連接多個槽。

對於同步調用:是一個接着一個調用的,執行順序類似上面,所以值也是每次調用也會變化的。

對於異步調用:其內容確實不確定的,因為異步調用的時間是不可控的。如果還有跨線程相關,則還有線程安全問題。

5.信號槽性能損失:

注:僅僅代碼層進行的理論分析,非實際測試,不嚴謹,不權威。

關於信號槽(很多吐槽Qt就是說的這個):

(1)Qt4語法的,都說是匹配字符串,其實只是鏈接信號槽的用的匹配字符串 的方法,通過字符串找到信號和槽在QMeatObject里存的索引位置int類型,還有槽函數的索引,然后調用的時候通過索引號用switch去區分的 發射的那個函數,然后取出對應的鏈接槽的list,循環檢測槽函數的參數是否匹配,然后調用槽函數。。這個鏈接時會耗時查找,但是你能有多少信號?這個鏈 接也耗時不多,調用的時候耗時主要就是在參數匹配上了。

(2)Qt5 語法的,Qt5 的槽函數鏈接和執行是基於模板實現的,函數對象。信號和槽的參數問題是編譯時檢查的,執行效率更高,但是編譯就慢點了。鏈接時也是通過信號的地址找到其的 信號索引,至於槽函數直接是生成一個函數對象的,然后調用的時候也是先switch找到發射的信號,取出list,然后逐個調用其儲存的函數對象,所以對 於Qt5 語法的信號槽,調用性能損失幾乎可以說無的。

(3)鏈接的信號槽的時候,Qt::UniqueConnection的鏈接方式會對已經鏈接過的此先好的槽函數進行遍歷,會有鏈接時的損失。其他鏈接的損失就在上面說過了。
(3)在信號槽調用的時候,還有一些鏈接方式和線程的判斷和為了安全問題的鎖操作。關於這個就還涉及到調用槽函數的線程問題。

對於同線程直接調用,較函數對象直接調用的損失,就只有鏈接方式和線程的判斷的幾個if 分支和 鎖的操作。
對於線程間通訊的調用,跨線程。信號槽內部也是通過Qt事件循環機制實現的,跨線程就不是時時調用了,主要是安全了,對於性能有沒有損失沒法評論的。對於跨線程阻塞的調用,這個也是事件實現,只是但發射信號的線程會阻塞,這個找不到對應的直接調用的比較,也不好說。
關於信號槽Qt是作何很多方便使用和安全調用,較之函數指針,性能會有損失,但是也沒損失多少的。對於函數對象調用,Qt5語法的調用,幾乎是不損失什么的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM