虛假喚醒
最近在使用Linux條件變量的時候,經過反復測試發現,pthread_cond_signal有時候會喚起多個正在pthread_cond_wait的線程。後來通過查閱IEEE Std 1003.1, 2004中關於pthread_cond_signal虛假喚醒(spurious wakeup)的解釋如下:
On a multi-processor, it may be impossible for an implementation of pthreadcondsignal() to avoid the unblocking of more than one thread blocked on a condition variable.
根據這個解釋,在多處理器系統上,pthread_cond_signal是很有可能喚醒多個pthread_cond_wait()的線程。也就意味著當一個線程中,pthread_cond_wait()返回的時候,不一定代表條件已經滿足了,需要在程序中做額外的判斷來檢測是否真的已經滿足條件了:
1 pthread_mutex_lock(&lock);
2 while (condition_is_false) {
3 pthread_cond_wait(&cond, &lock);
4 }
5 pthread_mutex_unlock(&lock);
事實上,IEEE Std 1003.1, 2004中有提到,虛假喚醒(spurious wakeup)是被允許的,而且鼓勵程序開發者在pthread_cond_wait()返回的時候對條件進行重新檢查,只有在條件滿足的情況下才繼續往下執行,否則就需要繼續等待了。
關於多處理器系統出現虛假喚醒(sprious wakeup)的原因,我的理解是因為多處理器上,多線程共享的數據需要在多核處理器上cache進行更新和拷貝的原因。關於多核多線程請參考《利用多核多線程進行程序優化》
消息遺漏
對於pthread_cond_signal或者pthread_cond_broadcast來說,除了需要在pthread_cond_wait()返回的時候,重新對條件進行檢查和評估以外,還有一件事情就是需要解決消息遺漏的問題。
根據pthread_cond_wait的定義,需要在pthread_cond_wait調用前後必須進行加鎖和解鎖操作。原因是因為如果在一個線程調用pthread_cond_wait的過程中但未進入block狀態,此時有線程調用了pthread_cond_signal或者pthread_cond_broadcast,那麼此次消息將被遺漏掉,因為沒有任何線程在pthread_cond_wait的block狀態。在pthread_cond_wait的實現內部,首先會解鎖,然後進入block狀態,解鎖和進入block必須合並成一個原子操作,這樣就保證了在pthread_cond_wait之後調用的pthread_cond_signal不會被以後掉。
但是對於多線程來說,pthread_cond_wait不能保證一定在pthread_cond_signal之後執行,也就意味著,當pthread_cond_wait進入block之後,已經錯過了pthread_cond_signal。因為已經錯過了pthread_cond_signal,很有可能會導致該線程永遠block下去。通常這類問題的解決辦法是設置一個pthread_cond_signal或者pthread_cond_broadcast的計數器count,在調用pthread_cond_wait之前先對這個count進行判斷,如果count != 0 則說明已經錯過了消息,可以不用等待,直接往下執行即可:
1 if (!count) {
2 pthread_mutex_lock(&lock);
3 while (condition_is_false) {
4 pthread_cond_wait(&cond, &lock);
5 }
6 pthread_mutex_unlock(&lock);
7 }