Qt QWaitCondition 的正確使用方法


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

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

 1 // 示例一  2 
 3 // 主線程
 4 Send(&packet);  5 mutex.lock();  6 condition.wait(&mutex);  7 if (m_receivedPacket)  8 {  9     HandlePacket(m_receivedPacket); // 另一線程傳來回包
10 } 11 mutex.unlock(); 12 
13 
14 // 通信線程
15 m_receivedPacket = ParsePacket(buffer);  // 將接收的數據解析成包
16 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() 解鎖。

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

 1 // 示例二  2 
 3 // 主線程
 4 mutex.lock();  5 Send(&packet);  6 condition.wait(&mutex);  7 if (m_receivedPacket)  8 {  9     HandlePacket(m_receivedPacket); // 另一線程傳來回包
10 } 11 mutex.unlock(); 12 
13 
14 // 通信線程
15 m_receivedPacket = ParsePacket(buffer);  // 將接收的數據解析成包
16 mutex.lock(); 17 condition.wakeAll(); 18 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 也都適用。


免責聲明!

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



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