1. 概述
條件變量(condition variable)是利用共享的變量進行線程之間同步的一種機制。典型的場景包括生產者-消費者模型,線程池實現等。
對條件變量的使用包括兩個動作:
1) 線程等待某個條件, 條件爲真則繼續執行,條件爲假則將自己掛起(避免busy wait,節省CPU資源);
2) 線程執行某些處理之後,條件成立;則通知等待該條件的線程繼續執行。
3) 爲了防止race-condition,條件變量總是和互斥鎖變量mutex結合在一起使用。
一般的編程模式:
- var mutex;
- var cond;
- var something;
- Thread1: (等待線程)
- lock(mutex);
- while( something not true ){
- condition_wait( cond, mutex);
- }
- do(something);
- unlock(mutex);
- ============================
- Thread2: (解鎖線程)
- do(something);
- ....
- something = true;
- unlock(mutex);
- condition_signal(cond);
函數說明:
(1) Condition_wait():調用時當前線程立即進入睡眠狀態,同時互斥變量mutex解鎖(這兩步操作是原子的,不可分割),以便其它線程能進入臨界區修改變量。
(2) Condition_signal(): 線程調用此函數後,除了當前線程繼續往下執行以外; 操作系統同時做如下動作:從condition_wait()中進入睡眠的線程中選一個線程喚醒, 同時被喚醒的線程試圖鎖(lock)住互斥量mutex, 當成功鎖住後,線程就從condition_wait()中成功返回了。
2. 函數接口
- pthread: pthread_cond_wait/pthread_cond_signal/pthread_cond_broadcast()
- Java: Condition.await()/Condition.signal()/Condition.signalAll()
3. 虛假喚醒(spurious wakeup)在採用條件等待時,我們使用的是
- while(條件不滿足){
- condition_wait(cond, mutex);
- }
- 而不是:
- If( 條件不滿足 ){
- Condition_wait(cond,mutex);
- }
這是因爲可能會存在虛假喚醒”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