http://www./u/12592/showart_2213910.html 最近在溫習pthread的時候,忽然發(fā)現(xiàn)以前對pthread_cond_wait的了解太膚淺了。昨晚在看《Programming With POSIX Threads》的時候,看到了pthread_cond_wait的通常使用方法: pthread_mutex_lock(); while(condition_is_false) pthread_cond_wait(); pthread_mutex_unlock(); 為什么在pthread_cond_wait()前要加一個while循環(huán)來判斷條件是否為假呢? APUE中寫道: 傳遞給pthread_cond_wait的互斥量對條件進行保護,調(diào)用者把鎖住的互斥量傳給函數(shù)。函數(shù)把調(diào)用線程放到等待條件的線程列表上,然后對互斥量解鎖,這兩個操作是原子操作。 線 程釋放互斥量,等待其他線程發(fā)給該條件變量的信號(喚醒一個等待者)或廣播該條件變量(喚醒所有等待者)。當?shù)却龡l件變量時,互斥量必須始終為釋放的,這 樣其他線程才有機會鎖住互斥量,修改條件變量。當線程從條件變量等待中醒來時,它重新繼續(xù)鎖住互斥量,對臨界資源進行處理。 條件變量的作用是發(fā)信號,而不是互斥。 wait前檢查 對 于多線程程序,不能夠用常規(guī)串行的思路來思考它們,因為它們是完全異步的,會出現(xiàn)很多臨界情況。比如:pthread_cond_signal的時間早于 pthread_cond_wait的時間,這樣pthread_cond_wait就會一直等下去,漏掉了之前的條件變化。 對于這種情況,解決的方法是在鎖住互斥量之后和等待條件變量之前,檢查條件變量是否已經(jīng)發(fā)生變化。 if(condition_is_false) pthread_cond_wait(); 這樣在等待條件變量前檢查一下條件變量的值,如果條件變量已經(jīng)發(fā)生了變化,那么就沒有必要進行等待了,可以直接進行處理。這種方法在并發(fā)系統(tǒng)中比較常見,例如之前PACKET_MMAP中poll的競爭條件的解決方法。 ----------------------------------------------------------------------- 忽然想起了設(shè)計模式中的單件模式的"雙重檢查加鎖": Singleton *getInstance() { if(ptr==NULL) { LOCK(); if(ptr==NULL) { ptr = new Singleton(); } UNLOCK(); } return ptr; } 這樣只有在第一次的時候會進行鎖(應該是第一輪,如果剛開始有多個線程進入了最上層的ptr==NULL代碼塊,就會有多次鎖,只不過之后就不會鎖了),之后就不會鎖了。 pthread_once()的實現(xiàn)也是基于單件模式的。 pthread_once 函數(shù)首先檢查控制變量,以判斷是否已經(jīng)完成初始化。如果完成,pthread_once簡單的返回;否則,pthread_once調(diào)用初始化函數(shù)(沒有 參數(shù)),并記錄下初始化被完成。如果在一個線程初始化時,另外的線程調(diào)用pthread_once,則調(diào)用線程將等待,直到那個線程完成初始化后返回。換 句話,當調(diào)用pthread_once成功返回時,調(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()不僅僅在等待條件變量前檢查條件變量,實際上在等待條件變量后也檢查條件變量。pthread_cond_wait返回后,還需要檢查 條件變量,這是為什么呢?難道pthread_cond_wait不是pthread_cond_signal觸發(fā)了某個condition導致的嗎? 這個地方有些迷惑人,實際上pthread_cond_wait的返回不僅僅是pthread_cond_signal和pthread_cond_broadcast導致的,還會有一些假喚醒,也就是spurious wakeup。 何為假喚醒?顧名思義就是虛假的喚醒,與pthread_cond_signal和pthread_cond_broadcast的喚醒相對。那么什么情況下會導致假喚醒呢?可以閱讀參考1。 signal 大致意思是: 在linux中,pthread_cond_wait底層是futex系統(tǒng)調(diào)用。在linux中,任何慢速的阻塞的系統(tǒng)調(diào)用當接收到信號的時候,就會返回-1,并且設(shè)置errno為EINTR。在系統(tǒng)調(diào)用返回前,用戶程序注冊的信號處理函數(shù)會被調(diào)用處理。
注:什么有樣的系統(tǒng)調(diào)用會出現(xiàn)接收信號后發(fā)揮EINTR呢? 慢速阻塞的系統(tǒng)調(diào)用,有可能會永遠阻塞下去的那種。當接收到信號的時候,認為是一個返回并執(zhí)行其他代碼的一個時機。 信 號的處理也不簡單,因為有些慢系統(tǒng)調(diào)用被信號中斷后是會自動重啟的,所以我們通常需要用siginterrupt(signo, 1)來關(guān)閉重啟或者在用sigaction安裝信號處理函數(shù)的時候取消SA_RESTART標志,之后就可以通過判斷信號的返回值是否是-1和errno 是否為EINTR來判斷是否有信號抵達。 如果關(guān)閉了SA_RESTART的一些使用慢速系統(tǒng)調(diào)用的應用,一般都采用while()循環(huán),檢測到EINTR后就重新調(diào)用。 while(1) { int ret = syscall(); if(ret<0 && errno==EINTR) continue; else break; } 但 是,對于futex這種方法不行,因為futex結(jié)束后,再重新運行的過程中,會出現(xiàn)一個時間窗口,其他線程可能會在這個時間窗口中進行 pthread_cond_signal,這樣,再進行pthread_cond_wait的時候就丟失了一次條件變量的變化。解決方法就是在 pthread_cond_wait前檢查條件變量,也就是 pthread_mutex_lock(); while(condition_is_false) pthread_cond_wait(); pthread_mutex_unlock(); pthread_cond_broadcast 實 際上,不僅僅信號會導致假喚醒,pthread_cond_broadcast也會導致假喚醒。加入條件變量上有多個線程在等 待,pthread_cond_broadcast會喚醒所有的等待線程,而pthread_cond_signal只會喚醒其中一個等待線程。這 樣,pthread_cond_broadcast的情況也許要在pthread_cond_wait前使用while循環(huán)來檢查條件變量。 參考: http://vladimir_prus./2005/07/spurious-wakeups.html http://groups./group/comp.programming.threads/msg/bb8299804652fdd7 |
|