Linux執行緒同步之條件變數pthread_cond_t
Linux執行緒同步之條件變數pthread_cond_t
一直以來都天真的認為執行緒間同步的方法只有訊號量,互斥量,郵箱,訊息佇列,知道最近開始研究一些Linux方面的程式碼才發現自己是多麼的很傻很天真。在Linux中還存在這一種叫做條件變數的東西。必須承認我在理解這個概念上花了很多時間,查閱了很多資料。這裡主要分析如下幾個問題:1. 條件變數是什麼;2.為什麼要和互斥量配合使用,互斥量保護的是什麼;3.為什麼條件變數經常會和while配合使用。
1. 什麼是條件變數
條件變數是執行緒同步的一種手段。條件變數用來自動阻塞一個執行緒,直到條件(predicate)滿足被觸發為止。通常情況下條件變數和互斥鎖同時使用。
條件變數使我們可以睡眠等待某種條件出現。條件變數是利用執行緒間共享的全域性變數進行同步的一種機制,主要包括兩個動作:一個/多個執行緒等待"條件變數的條件成立"而掛起;另一個執行緒使"條件成立"訊號。
2. Mutex的作用
這裡讓我們先用一段程式碼說明一般條件變數是如何和互斥量配合使用的:
1 int WaitForPredicate() 2 { 3 // lock mutex (means:lock access to the predicate) 4 pthread_mutex_lock(&mtx); 5 6 // we can safely check this, since no one else should be 7 // changing it unless they have the mutex, which they don't 8 // because we just locked it. 9 while (!predicate) 10 { 11 // predicate not met, so begin waiting for notification 12 // it has been changed *and* release access to change it 13 // to anyone wanting to by unlatching the mutex, doing 14 // both (start waiting and unlatching) atomically 15 pthread_cond_wait(&cv,&mtx); 16 } 17 18 // we own the mutex here. further, we have assessed the 19 // predicate is true (thus how we broke the loop). 20 21 // You *must* release the mutex before we leave. 22 pthread_mutex_unlock(&mtx); 23 }
那這個互斥量保護的是什麼呢?是條件變數本身麼?並不是!mutex使用來保護predicate。mutex被成功lock後我們就可以放心的去讀取predicate的值,而不用擔心在這期間predicate會被其他執行緒修改。如果predicate不滿足條件,當前執行緒阻塞等待其他執行緒釋放條件成立訊號,並釋放已經lock的mutex。這樣一來其他執行緒就有了修改predicate的機會。當其他執行緒釋放條件成立訊號後,pthread_cond_wait函式返回,並再次lock mutex。
pthread_cond_wait的工作流程可以總結為:unlock mutex,start waiting -> lock mutex。
3. while的作用
在上面的程式碼當中可以看到,predicate是用while來檢查的而不是用if在做判斷。這樣做的原因是,pthread_cond_wait的返回並不一定意味著其他執行緒釋放了條件成立訊號。而是意外返回。這種情況稱為Spurious wakeup。之所以這樣做的原因是從效率上考慮的。Volodya's blog - Spurious wakeups裡有很詳細的一個講解,簡單來說造成Spurious wakeup的原因在於,Linux中帶阻塞功能的system call都會在程序收到了一個signal後返回。這就是為什麼要用while來檢查的原因。因為我們並不能保證wait函式返回就一定是條件滿足,如果條件不滿足,還需要繼續等待。
4. Signal條件變數時的考慮
因為在signal執行緒中解鎖互斥量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次的上下文切換,影響效能. 那使用(1)又是什麼情況呢,在unlock之後,其他的執行緒就可以獲取這個mutex進而修改了predicate,這樣signal就失去了意義,也和我們的最初想法不一樣。就我個人而言,我會選擇使用(2)。
本部落格全部文章(除文章標題或文內有另外特別說明的)皆為本人辛苦碼字而得,本人擁有其版權。 轉載時請以超連結形式標明文章原始出處和作者。謝謝
https://www.cnblogs.com/zhx831/p/3543633.html
https://www.cnblogs.com/xudong-bupt/p/6707070.html