多線程編程中條件變量和虛假喚醒(spurious wakeup)的討論

多線程編程中條件變量和虛假喚醒的討論

1. 概述
條件變量(condition variable)是利用共享的變量進行線程之間同步的一種機制。典型的場景包括生產者-消費者模型,線程池實現等。
對條件變量的使用包括兩個動作:
1) 線程等待某個條件, 條件爲真則繼續執行,條件爲假則將自己掛起(避免busy wait,節省CPU資源);
2) 線程執行某些處理之後,條件成立;則通知等待該條件的線程繼續執行。
3) 爲了防止race-condition,條件變量總是和互斥鎖變量mutex結合在一起使用。

一般的編程模式:
Java代碼  收藏代碼
  1. var mutex;  
  2. var cond;  
  3. var something;  
  4.   
  5. Thread1: (等待線程)  
  6. lock(mutex);  
  7. while( something not true ){  
  8.     condition_wait( cond, mutex);  
  9. }  
  10. do(something);  
  11. unlock(mutex);  
  12.   
  13. ============================  
  14.   
  15. Thread2: (解鎖線程)  
  16.   
  17. do(something);  
  18. ....  
  19. something = true;  
  20.   
  21. unlock(mutex);  
  22. condition_signal(cond);  

函數說明:
(1) Condition_wait():調用時當前線程立即進入睡眠狀態,同時互斥變量mutex解鎖(這兩步操作是原子的,不可分割),以便其它線程能進入臨界區修改變量。
(2) Condition_signal(): 線程調用此函數後,除了當前線程繼續往下執行以外; 操作系統同時做如下動作:從condition_wait()中進入睡眠的線程中選一個線程喚醒, 同時被喚醒的線程試圖鎖(lock)住互斥量mutex, 當成功鎖住後,線程就從condition_wait()中成功返回了。

2. 函數接口

Java代碼  收藏代碼
  1. pthread: pthread_cond_wait/pthread_cond_signal/pthread_cond_broadcast()  
  2. Java: Condition.await()/Condition.signal()/Condition.signalAll()  


3. 虛假喚醒(spurious wakeup)在採用條件等待時,我們使用的是
Java代碼  收藏代碼
  1. while(條件不滿足){  
  2.    condition_wait(cond, mutex);  
  3. }  
  4. 而不是:  
  5. If( 條件不滿足 ){  
  6.    Condition_wait(cond,mutex);  
  7. }   


這是因爲可能會存在虛假喚醒”spurious wakeup”的情況。

也就是說,即使沒有線程調用condition_signal, 原先調用condition_wait的函數也可能會返回。此時線程被喚醒了,但是條件並不滿足,這個時候如果不對條件進行檢查而往下執行,就可能會導致後續的處理出現錯誤。
虛假喚醒在linux的多處理器系統中/在程序接收到信號時可能回發生。在Windows系統和JAVA虛擬機上也存在。在系統設計時應該可以避免虛假喚醒,但是這會影響條件變量的執行效率,而既然通過while循環就能避免虛假喚醒造成的錯誤,因此程序的邏輯就變成了while循環的情況。
注意:即使是虛假喚醒的情況,線程也是在成功鎖住mutex後才能從condition_wait()中返回。即使存在多個線程被虛假喚醒,但是也只能是一個線程一個線程的順序執行,也即:lock(mutex)   檢查/處理  condition_wai()或者unlock(mutex)來解鎖.

4. 解鎖和等待轉移(wait morphing)

解鎖互斥量mutex和發出喚醒信號condition_signal是兩個單獨的操作,那麼就存在一個順序的問題。誰先隨後可能會產生不同的結果。如下:
(1) 按照 unlock(mutex); condition_signal()順序, 當等待的線程被喚醒時,因爲mutex已經解鎖,因此被喚醒的線程很容易就鎖住了mutex然後從conditon_wait()中返回了。
(2) 按照 condition_signal(); unlock(mutext)順序,當等待線程被喚醒時,它試圖鎖住mutex,但是如果此時mutex還未解鎖,則線程又進入睡眠,mutex成功解鎖後,此線程在再次被喚醒並鎖住mutex,從而從condition_wait()中返回。


可以看到,按照(2)的順序,對等待線程可能會發生2次的上下文切換,嚴重影響性能。因此在後來的實現中,對(2)的情況,如果線程被喚醒但是不能鎖住mutex,則線程被轉移(morphing)到互斥量mutex的等待隊列中,避免了上下文的切換造成的開銷。 -- wait morphing

編程時,推薦採用(1)的順序解鎖和發喚醒信號而Java編程只能按照(2)的順序,否則發生異常!!。

在SUSv3http://en.wikipedia.org/wiki/Single_UNIX_Specification的規範中(pthread),指明瞭這兩種順序不管採用哪種,其實現效果都是一樣的。


轉自http://siwind.iteye.com/blog/1469216

發佈了0 篇原創文章 · 獲贊 1 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章