pthread_cond_wait

pthread_cond_wait的spurious wakeup問題 最近在溫習pthread的時候,忽然發現以前對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循環來判斷條件是否爲假呢? APUE中寫道: 傳遞給pthread_cond_wait的互斥量對條件進行保護,調用者把鎖住的互斥量傳給函數。函數把調用線程放到等待條件的線程列表上,然後對互斥量解鎖,這兩個操作是原子操作。 線程釋放互斥量,等待其他線程發給該條件變量的信號(喚醒一個等待者)或廣播該條件變量(喚醒所有等待者)。當等待條件變量時,互斥量必須始終爲釋放的,這樣其他線程纔有機會鎖住互斥量,修改條件變量。當線程從條件變量等待中醒來時,它重新繼續鎖住互斥量,對臨界資源進行處理。 條件變量的作用是發信號,而不是互斥。 wait前檢查 對於多線程程序,不能夠用常規串行的思路來思考它們,因爲它們是完全異步的,會出現很多臨界情況。比如:pthread_cond_signal的時間早於pthread_cond_wait的時間,這樣pthread_cond_wait就會一直等下去,漏掉了之前的條件變化。 對於這種情況,解決的方法是在鎖住互斥量之後和等待條件變量之前,檢查條件變量是否已經發生變化。 if(condition_is_false) pthread_cond_wait(); 這樣在等待條件變量前檢查一下條件變量的值,如果條件變量已經發生了變化,那麼就沒有必要進行等待了,可以直接進行處理。這種方法在併發系統中比較常見,例如之前PACKET_MMAP中poll的競爭條件的解決方法。 ----------------------------------------------------------------------- 忽然想起了設計模式中的單件模式的"雙重檢查加鎖": Singleton *getInstance() { if(ptr==NULL) { LOCK(); if(ptr==NULL) { ptr = new Singleton(); } UNLOCK(); } return ptr; } 這樣只有在第一次的時候會進行鎖(應該是第一輪,如果剛開始有多個線程進入了最上層的ptr==NULL代碼塊,就會有多次鎖,只不過之後就不會鎖了),之後就不會鎖了。 pthread_once()的實現也是基於單件模式的。 pthread_once函數首先檢查控制變量,以判斷是否已經完成初始化。如果完成,pthread_once簡單的返回;否則,pthread_once調用初始化函數(沒有參數),並記錄下初始化被完成。如果在一個線程初始化時,另外的線程調用pthread_once,則調用線程將等待,直到那個線程完成初始化後返回。換句話,當調用pthread_once成功返回時,調用者能夠肯定所有的狀態已經初始化完畢。 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觸發了某個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系統調用。在linux中,任何慢速的阻塞的系統調用當接收到信號的時候,就會返回-1,並且設置errno爲EINTR。在系統調用返回前,用戶程序註冊的信號處理函數會被調用處理。 注:什麼有樣的系統調用會出現接收信號後發揮EINTR呢? 慢速阻塞的系統調用,有可能會永遠阻塞下去的那種。當接收到信號的時候,認爲是一個返回並執行其他代碼的一個時機。 信號的處理也不簡單,因爲有些慢系統調用被信號中斷後是會自動重啓的,所以我們通常需要用siginterrupt(signo, 1)來關閉重啓或者在用sigaction安裝信號處理函數的時候取消SA_RESTART標誌,之後就可以通過判斷信號的返回值是否是-1和errno是否爲EINTR來判斷是否有信號抵達。 如果關閉了SA_RESTART的一些使用慢速系統調用的應用,一般都採用while()循環,檢測到EINTR後就重新調用。 while(1) { int ret = syscall(); if(ret<0 && errno==EINTR) continue; else break; } 但是,對於futex這種方法不行,因爲futex結束後,再重新運行的過程中,會出現一個時間窗口,其他線程可能會在這個時間窗口中進行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循環來檢查條件變量。 參考: 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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章