天天看點

轉:Qt信号槽的一些事

原文:https://www.dushibaiyu.com/2015/07/qt-signals-slots-connect.html#comment-2232

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

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

Qt::AutoConnection 自動連接配接:預設的方式。信号發出的線程和糟的對象在一個線程的時候相當于: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連接配接的,如果發出信号的線程和槽函數線程不同亦然。

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文法的調用,幾乎是不損失什麼的。