pthread_cond_wait的spurious wakeup问题
最近在溫習(xí)pthread的時(shí)候,忽然發(fā)現(xiàn)以前對(duì)pthread_cond_wait的了解太膚淺了。昨晚在看《Programming With POSIX Threads》的時(shí)候,看到了pthread_cond_wait的通常使用方法:
pthread_mutex_lock();
while(condition_is_false)
??? pthread_cond_wait();
pthread_mutex_unlock();
為什么在pthread_cond_wait()前要加一個(gè)while循環(huán)來判斷條件是否為假呢?
APUE中寫道:
傳遞給pthread_cond_wait的互斥量對(duì)條件進(jìn)行保護(hù),調(diào)用者把鎖住的互斥量傳給函數(shù)。函數(shù)把調(diào)用線程放到等待條件的線程列表上,然后對(duì)互斥量解鎖,這兩個(gè)操作是原子操作。
線程釋放互斥量,等待其他線程發(fā)給該條件變量的信號(hào)(喚醒一個(gè)等待者)或廣播該條件變量(喚醒所有等待者)。當(dāng)?shù)却龡l件變量時(shí),互斥量必須始終為釋放的,這樣其他線程才有機(jī)會(huì)鎖住互斥量,修改條件變量。當(dāng)線程從條件變量等待中醒來時(shí),它重新繼續(xù)鎖住互斥量,對(duì)臨界資源進(jìn)行處理。
條件變量的作用是發(fā)信號(hào),而不是互斥。
wait前檢查
對(duì)于多線程程序,不能夠用常規(guī)串行的思路來思考它們,因?yàn)樗鼈兪峭耆惒降?#xff0c;會(huì)出現(xiàn)很多臨界情況。比如:pthread_cond_signal的時(shí)間早于pthread_cond_wait的時(shí)間,這樣pthread_cond_wait就會(huì)一直等下去,漏掉了之前的條件變化。
對(duì)于這種情況,解決的方法是在鎖住互斥量之后和等待條件變量之前,檢查條件變量是否已經(jīng)發(fā)生變化。
if(condition_is_false)
??? pthread_cond_wait();
這樣在等待條件變量前檢查一下條件變量的值,如果條件變量已經(jīng)發(fā)生了變化,那么就沒有必要進(jìn)行等待了,可以直接進(jìn)行處理。這種方法在并發(fā)系統(tǒng)中比較常見,例如之前PACKET_MMAP中poll的競(jìng)爭(zhēng)條件的解決方法。
-----------------------------------------------------------------------
忽然想起了設(shè)計(jì)模式中的單件模式的"雙重檢查加鎖":
Singleton *getInstance()
{
??? if(ptr==NULL)
??? {
??????? LOCK();
??????? if(ptr==NULL)
??????? {
??????????? ptr = new Singleton();
??????? }
??????? UNLOCK();
??? }
??? return ptr;
}
這樣只有在第一次的時(shí)候會(huì)進(jìn)行鎖(應(yīng)該是第一輪,如果剛開始有多個(gè)線程進(jìn)入了最上層的ptr==NULL代碼塊,就會(huì)有多次鎖,只不過之后就不會(huì)鎖了),之后就不會(huì)鎖了。
pthread_once()的實(shí)現(xiàn)也是基于單件模式的。
pthread_once函數(shù)首先檢查控制變量,以判斷是否已經(jīng)完成初始化。如果完成,pthread_once簡(jiǎn)單的返回;否則,pthread_once調(diào)用初始化函數(shù)(沒有參數(shù)),并記錄下初始化被完成。如果在一個(gè)線程初始化時(shí),另外的線程調(diào)用pthread_once,則調(diào)用線程將等待,直到那個(gè)線程完成初始化后返回。換句話,當(dāng)調(diào)用pthread_once成功返回時(shí),調(diào)用者能夠肯定所有的狀態(tài)已經(jīng)初始化完畢。
int
__pthread_once (once_control, init_routine)
???? pthread_once_t *once_control;
???? void (*init_routine) (void);
{
? /* XXX Depending on whether the LOCK_IN_ONCE_T is defined use a
???? global lock variable or one which is part of the pthread_once_t
???? object.? */
? if (*once_control == PTHREAD_ONCE_INIT)
??? {
????? lll_lock (once_lock, LLL_PRIVATE);
????? /* XXX This implementation is not complete.? It doesn't take
cancelation and fork into account.? */
????? if (*once_control == PTHREAD_ONCE_INIT)
{
? init_routine ();
? *once_control = !PTHREAD_ONCE_INIT;
}
????? lll_unlock (once_lock, LLL_PRIVATE);
??? }
? return 0;
}
-----------------------------------------------------------------------
pthread_cond_wait中的while()不僅僅在等待條件變量前檢查條件變量,實(shí)際上在等待條件變量后也檢查條件變量。pthread_cond_wait返回后,還需要檢查條件變量,這是為什么呢?難道pthread_cond_wait不是pthread_cond_signal觸發(fā)了某個(gè)condition導(dǎo)致的嗎?
這個(gè)地方有些迷惑人,實(shí)際上pthread_cond_wait的返回不僅僅是pthread_cond_signal和pthread_cond_broadcast導(dǎo)致的,還會(huì)有一些假喚醒,也就是spurious wakeup。
何為假喚醒?顧名思義就是虛假的喚醒,與pthread_cond_signal和pthread_cond_broadcast的喚醒相對(duì)。那么什么情況下會(huì)導(dǎo)致假喚醒呢?可以閱讀參考1。
signal
大致意思是:
在linux中,pthread_cond_wait底層是futex系統(tǒng)調(diào)用。在linux中,任何慢速的阻塞的系統(tǒng)調(diào)用當(dāng)接收到信號(hào)的時(shí)候,就會(huì)返回-1,并且設(shè)置errno為EINTR。在系統(tǒng)調(diào)用返回前,用戶程序注冊(cè)的信號(hào)處理函數(shù)會(huì)被調(diào)用處理。
注:什么有樣的系統(tǒng)調(diào)用會(huì)出現(xiàn)接收信號(hào)后發(fā)揮EINTR呢?
慢速阻塞的系統(tǒng)調(diào)用,有可能會(huì)永遠(yuǎn)阻塞下去的那種。當(dāng)接收到信號(hào)的時(shí)候,認(rèn)為是一個(gè)返回并執(zhí)行其他代碼的一個(gè)時(shí)機(jī)。
信號(hào)的處理也不簡(jiǎn)單,因?yàn)橛行┞到y(tǒng)調(diào)用被信號(hào)中斷后是會(huì)自動(dòng)重啟的,所以我們通常需要用siginterrupt(signo, 1)來關(guān)閉重啟或者在用sigaction安裝信號(hào)處理函數(shù)的時(shí)候取消SA_RESTART標(biāo)志,之后就可以通過判斷信號(hào)的返回值是否是-1和errno是否為EINTR來判斷是否有信號(hào)抵達(dá)。
如果關(guān)閉了SA_RESTART的一些使用慢速系統(tǒng)調(diào)用的應(yīng)用,一般都采用while()循環(huán),檢測(cè)到EINTR后就重新調(diào)用。
while(1)
{
?? int ret = syscall();
?? if(ret<0 && errno==EINTR)
?????? continue;
?? else
?????? break;
}
但是,對(duì)于futex這種方法不行,因?yàn)閒utex結(jié)束后,再重新運(yùn)行的過程中,會(huì)出現(xiàn)一個(gè)時(shí)間窗口,其他線程可能會(huì)在這個(gè)時(shí)間窗口中進(jìn)行pthread_cond_signal,這樣,再進(jìn)行pthread_cond_wait的時(shí)候就丟失了一次條件變量的變化。解決方法就是在pthread_cond_wait前檢查條件變量,也就是
pthread_mutex_lock();
while(condition_is_false)
??? pthread_cond_wait();
pthread_mutex_unlock();
pthread_cond_broadcast
實(shí)際上,不僅僅信號(hào)會(huì)導(dǎo)致假喚醒,pthread_cond_broadcast也會(huì)導(dǎo)致假喚醒。加入條件變量上有多個(gè)線程在等待,pthread_cond_broadcast會(huì)喚醒所有的等待線程,而pthread_cond_signal只會(huì)喚醒其中一個(gè)等待線程。這樣,pthread_cond_broadcast的情況也許要在pthread_cond_wait前使用while循環(huán)來檢查條件變量。
參考:
http://vladimir_prus.blogspot.com/2005/07/spurious-wakeups.html
http://www.lambdacs.com/cpt/FAQ.html#Q94
http://groups.google.de/group/comp.programming.threads/msg/bb8299804652fdd7
http://www.win.tue.nl/~aeb/linux/lk/lk-4.html#ss4.5
http://blog.chinaunix.net/u/5251/showart_309061.html
總結(jié)
以上是生活随笔為你收集整理的pthread_cond_wait的spurious wakeup问题的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Asterisk入门系列
- 下一篇: Android串口通信实例分析【附源码】