Qt的QWaitCondition了解一下吧


 QWaitConditon也是用來同步線程的。從名字來看是等待條件,意思就是線程阻塞在等待條件的地方,直到條件滿足才繼續執行下去。等待條件的線程可以是一個或者多個。用QWaitCondition的函數表示過程如下:

    1.等待條件的線程調用QWaitCondition::wait()阻塞。

    2.實現條件的線程通過計算完成條件后調用QWaitConditon::wakeOne()或者QWaitCondition::wakeAll()。

    3.當2中調用wake之后,繼續執行wait之后的操作。

    其中wakeOne會隨機喚醒等待的線程中的一個。wakeAll會喚醒所有的等待線程。

    

    寫個例子測試一下:

    1.創建兩個線程類Thread和ThreadTwo, Thread用來等待,ThreadTwo來喚醒。兩個類的run函數分別如下:

    blob.png

 

    blob.png

    Thread的run函數功能就是等待被喚醒,然后輸出一句話。ThreadTwo的run函數就是每隔2秒喚醒一次。

    2.創建2個Thread類的對象,和1個ThreadTwo的對象,並調用start。然后查看執行情況。

    blob.png

    blob.png

    程序運行后會按上圖中箭頭所划分,每兩秒輸出一部分。也就是說ThreadTwo每兩秒喚醒了一個Thread的線程。如果ThreadTwo調用的是wakeAll函數那么在輸出0時兩個線程都被喚醒,運行結果如下:

    blob.png

     說明:由於ThreadTwo的run函數中先調用了sleep確保了wake會在Thread的wait之后發生,因此沒有使用QMutex來保證他們的執行順序。

 

以下討論QMutex與QWaitConditon結合使用的情況:

簡單用法

QWaitCondition 用於多線程的同步,一個線程調用QWaitCondition::wait() 阻塞等待,直到另一個線程調用QWaitCondition::wake() 喚醒才繼續往下執行。

為了描述方便,這里假設主線程調用Send()往通信口發送一個數據包,然后阻塞等待回包才繼續往下執行。另一個線程(通信線程)不斷從通信口中接收數據並解析成數據包,然后喚醒主線程。下面是按網上給的最簡單的方法:

// 示例一 // 主線程 Send(&packet); mutex.lock(); condition.wait(&mutex); if (m_receivedPacket) { HandlePacket(m_receivedPacket); // 另一線程傳來回包 } mutex.unlock(); // 通信線程 m_receivedPacket = ParsePacket(buffer); // 將接收的數據解析成包 condition.wakeAll();

通常情況下,上述代碼能跑得很好。但在某些特殊情況下,可能會出現混亂,大大降低通信可靠性。

在主線程中,調用 Send(&packet) 發送后,假如通信線程立即收到回包,在主線程還來不及調用 wait() 的時候,已經先 wakeAll() 了,顯然這次喚醒是無效的,但主線程繼續調用 wait(),然后一直阻塞在那里,因為該回的包已經回了。經測試出現這種現象的概率還是挺大的,因為我們不敢保證主線程總會被優先調度。即使主線程已經調用了 wait(),也不能保證底層操作系統的 wait_block 系統調用先於 wake 系統調用,畢竟wait() 函數也是層層封裝的。

嚴謹用法

QWaitCondition::wait() 在使用時必須傳入一個上鎖的 QMutex 對象。這是很有必要的。而上述示例一代碼中,我們雖然用了 mutex,但只是為了形式上傳入QMutex參數,讓編譯器能正常編譯而已,事實上,沒有其它任何線程再用到這個mutex。而 mutex 本來就是讓多個線程能協調工作的,所以上述示例一主線程用的 mutex 是無效的。

根據 Qt 手冊,wait() 函數必須傳入一個已上鎖的 mutex 對象,在 wait() 執行過程中,mutex一直保持上鎖狀態,直到調用操作系統的wait_block 在阻塞的一瞬間把 mutex 解鎖(嚴格說來應該是原子操作,即系統能保證在真正執行阻塞等待指令時才解鎖)。另一線程喚醒后,wait() 函數將在第一時間重新給 mutex 上鎖(這種操作也是原子的),直到顯式調用 mutex.unlock() 解鎖(指的也是wait() 函數所在線程執行的unlock)

在通信線程也用上 mutex 后,整個通信時序正常了,完全解決了示例一的問題。代碼如下:

// 示例二 // 主線程 mutex.lock(); Send(&packet); condition.wait(&mutex); if (m_receivedPacket) { HandlePacket(m_receivedPacket); // 另一線程傳來回包 } mutex.unlock(); // 通信線程 m_receivedPacket = ParsePacket(buffer); // 將接收的數據解析成包 mutex.lock(); condition.wakeAll(); mutex.unlock();

上述示例二中,主線程先把 mutex 鎖占據,即從發送數據包開始,一直到 QWaitCondition::wait() 在操作系統層次真正執行阻塞等待指令,這一段主線程的時間段內,mutex 一直被上鎖,即使通信線程很快就接收到數據包,也不會直接調用 wakeAll(),而是在調用 mutex.lock() 時阻塞住因為主線程已經把mutex占據上鎖了,再嘗試上鎖就會被阻塞),直到主線程 QWaitCondition::wait() 真正執行操作系統的阻塞等待指令並釋放mutex,通信線程的 mutex.lock() 才解除阻塞,繼續往下執行,調用 wakeAll(),此時一定能喚醒主線程成功。

由此可見,通過 mutex 把有嚴格時序要求的代碼保護起來,同時把 wakeAll() 也用同一個 mutex 保護起來,這樣能保證一定先有 wait() ,再有 wakeAll(),不管什么情況,都能保證這種先后關系,而不至於擺烏龍。

推而廣之

mutex 和 condition 聯合使用是多線程中的一個常用的設計模式,不僅是 Qt,對於 C++ 的 std::condition_variable 和 std::mutex ,以及 java 的 synchronized / wait / notify 也都適用。

 

轉自:https://www.fearlazy.com/index.php/post/101.html

https://blog.csdn.net/flyoxs/article/details/54617342


免責聲明!

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



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