引用APUE中的一句話:linux
Condition variables are another synchronization mechanism available to threads.
These synchronization objects provide a place for threads to rendezvous. When used with mutexes, condition variables allow threads to wait in a race-free way for arbitrary conditions to occur.ide
條件變量是線程的另一種同步機制,這些同步對象爲線程提供了會合的場所,理解起來就是兩個(或者多個)線程須要碰頭(或者說進行交互-一個線程給另外的一個或者多個線程發送消息),咱們指定在條件變量這個地方發生,一個線程用於修改這個變量使其知足其它線程繼續往下執行的條件,其它線程則接收條件已經發生改變的信號。函數
條件變量同鎖一塊兒使用使得線程能夠以一種無競爭的方式等待任意條件的發生。所謂無競爭就是,條件改變這個信號會發送到全部等待這個信號的線程。而不是說一個線程接受到這個消息而其它線程就接收不到了。性能
具體的函數介紹就不說了,詳細參考APUE,下面經過一個例子來詳細說一下正確使用條件變量的方法。下例實現了生產者和消費者模型,生產者向隊列中插入數據,消費者則在生產者發出隊列準備好(有數據了)後接收消息,而後取出數據進行處理。實現的關鍵點在如下幾個方面:ui
代碼:atom
#include <pthread.h> struct msg { struct msg *m_next; /* ... more stuff here ... */ }; struct msg *workq; pthread_cond_t qready = PTHREAD_COND_INITIALIZER; pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER; void process_msg(void) { struct msg *mp; for (;;) { pthread_mutex_lock(&qlock); while (workq == NULL) pthread_cond_wait(&qready, &qlock); mp = workq; workq = mp->m_next; pthread_mutex_unlock(&qlock); /* now process the message mp */ } } void enqueue_msg(struct msg *mp) { pthread_mutex_lock(&qlock); mp->m_next = workq; workq = mp; pthread_mutex_unlock(&qlock); pthread_cond_signal(&qready); }
pthread_cond_wait中的mutex用於保護條件變量,調用這個函數進行等待條件的發生時,mutex會被自動釋放,以供其它線程(生產者)改變條件,pthread_cond_wait中的兩個步驟必須是原子性的(atomically,萬惡的APUE中文版把這個單詞翻譯成了『自動』,誤人子弟啊),也就是說必須把兩個步驟捆綁到一塊兒:.net
否則呢,若是不是原子性的,上面的兩個步驟中間就可能插入其它操做。好比,若是先釋放mutex,這時候生產者線程向隊列中添加數據,而後signal,以後消費者線程纔去『把調用線程放到等待隊列上』,signal信號就這樣被丟失了。線程
若是先把調用線程放到條件等待隊列上,這時候另一個線程發送了pthread_cond_signal(咱們知道這個函數的調用是不須要mutex的),而後調用線程當即獲取mutex,兩次獲取mutex會產生deadlock.翻譯
若是不這麼作信號可能會丟失,看下面的例子:設計
Thead A Thread B pthread_mutex_lock(&qlock); while (workq == NULL) mp->m_next = workq; workq = mp; pthread_cond_signal(&cond); pthread_cond_wait(&qready, &qlock);
在while判斷以後向隊列中插入數據,雖然已經有數據了,但線程A仍是調用了pthread_cond_wait等待下一個信號到來。。
while (workq == NULL) pthread_cond_wait(&qready, &qlock); mp = workq;
咱們把while換成if可不能夠呢?
if (workq == NULL) pthread_cond_wait(&qready, &qlock); mp = workq;
答案是不能夠,一個生產者可能對應着多個消費者,生產者向隊列中插入一條數據以後發出signal,而後各個消費者線程的pthread_cond_wait獲取mutex後返回,固然,這裏只有一個線程獲取到了mutex,而後進行處理,其它線程會pending在這裏,處理線程處理完畢以後釋放mutex,剛纔等待的線程中有一個獲取mutex,若是這裏用if,就會在當前隊列爲空的狀態下繼續往下處理,這顯然是不合理的。
void enqueue_msg(struct msg *mp) { pthread_mutex_lock(&qlock); mp->m_next = workq; workq = mp; pthread_mutex_unlock(&qlock); pthread_cond_signal(&qready); }
若是先unlock,再signal,若是這時候有一個消費者線程剛好獲取mutex,而後進入條件判斷,這裏就會判斷成功,從而跳過pthread_cond_wait,下面的signal就會不起做用;另一種狀況,一個優先級更低的不須要條件判斷的線程正好也須要這個mutex,這時候就會轉去執行這個優先級低的線程,就違背了設計的初衷。
void enqueue_msg(struct msg *mp) { pthread_mutex_lock(&qlock); mp->m_next = workq; workq = mp; pthread_cond_signal(&qready); pthread_mutex_unlock(&qlock); }
若是把signal放在unlock以前,消費者線程會被喚醒,獲取mutex發現獲取不到,就又去sleep了。浪費了資源.可是在LinuxThreads或者NPTL裏面,就不會有這個問題,由於在Linux 線程中,有兩個隊列,分別是cond_wait隊列和mutex_lock隊列, cond_signal只是讓線程從cond_wait隊列移到mutex_lock隊列,而不用返回到用戶空間,不會有性能的損耗。
因此在Linux中推薦使用這種模式。
References:
why pthread_cond_wait need an lock?
Calling pthread_cond_signal without locking mutex
Why do pthreads’ condition variable functions require a mutex?