編寫同步隊列時,有用到條件變量,對操作隊列的線程進行同步。當隊列爲空時,允許get線程掛起,直到add線程向隊列添加元素並通過喚醒條件變量,get線程繼續向下運行。條件變量在多線程程序中用來實現“等待->喚醒”邏輯常用的方法。條件變量要和互斥量相聯結,以避免出現條件競爭:一個線程預備等待一個條件變量,當它在真正進入等待之前,另一個線程恰好觸發了該條件。使用條件變量進行同步時,通常以如下方式進行編碼:
pthread_mutex_t mutex;
pthread_cond_t cond;
//Get線程
int Get()
{
pthread_mutex_lock(&mutex);
while (queue.empty())
{
pthread_cond_wait(&cond, &mutex);
}
item = queue.front();
pthread_mutex_unlock(&mutex);
return item;
}
//Add線程
void Add(int item)
{
pthread_mutex_lock(&mutex);
queue.push(item);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
}
pthread_cond_wait函數在把線程放進阻塞隊列後,自動對mutex進行解鎖,使得其它線程可以獲得加鎖的權利。注意這是一個原子操作,不會造成條件檢查和線程進入休眠狀態等待之間有其他線程進入的問題,並使當前線程阻塞在cond參數指向的條件變量上,當此線程因條件不滿足而進入休眠後,因爲互斥量已經解鎖,所以別的線程可以對互斥量加鎖並改變臨界資源,從而喚醒當前阻塞的線程。Add線程獲取到鎖之後,隊列中添加元素,併發送信號喚醒阻塞在條件變量上的Get線程。當有多個Get線程阻塞在條件變量上時,可能會出現徐假喚醒,即阻塞的多個線程都認爲自己已經滿足條件了,而實際上可能隊列中的元素已經被第一個喚醒的線程Get走了,所以這裏必須用while(queue.empty())而不是if(queue.empty())進行判斷。
不知道大家有沒有注意到在Add函數中line21和line22,pthread_cond_signal和pthread_mutex_unlock的順序,即喚醒和解鎖的先後順序是否對程序的運行有何影響。這裏簡單的分析下,我們知道Add線程向隊列中添加元素之後,會調用pthread_cond_signal,將阻塞在Get操作的線程喚醒,那麼此時阻塞在pthread_cond_wait() 處的線程將甦醒,並進行返回,這裏要注意,並非Add線程調用pthread_cond_signal函數後,pthread_cond_wait() 就立即返回,而是在它獲取到mutex並重新lock,才能返回繼續向下執行。根據分析想到的結論是,如果先signal再unlock,那麼當前阻塞在pthread_cond_wait上的線程可能會立即獲取到mutex(因爲可能有其他Add線程阻塞在mutex上,所以並不一定獲取到mutex),重新lock,返回後向下執行;而如果先unlock再signal,那麼解鎖後mutex可能已經被其他線程獲取到了,那麼signal喚醒pthread_cond_wait就會繼續等待mutex重新被獲取走的線程釋放,可能會比前一種情況等待的時間長些,由其他的Add線程將其喚醒。不太確定分析的是否正確,於是在Stack Overflow上看到了同樣的問題,建議將signal放在unlock之前,部分翻譯總結如下:
如果Add函數將unlock在前,signal在後,可能會產生Spurious wakeups,考慮如下場景:
1. 線程A阻塞在Get函數,解鎖,等待Add操作向隊列中添加item。
2. 線程B調用Add函數,向隊列中添加item。在Add函數unlock之後,還未signal之前,發送了上下文切換。
3. 線程C獲取到mutex,調用Add函數,向隊列中添加item,解鎖並且調用signal函數。
4. 此時線程A獲取到mutex,wait函數返回,處理了剛纔Add進來的兩個item,之後繼續阻塞在條件變量上。
5. 此時如果線程B得到CPU時間片,那麼繼續從2處運行,調用signal,喚醒A線程
6. 線程A被喚醒,但是因爲之前的item已經被它Get出來,所以此時隊列仍然爲空,所以線程A再次進入阻塞狀態。
參考:
http://stackoverflow.com/questions/6312342/pthread-cond-wait-and-mutex-requirement
http://stackoverflow.com/questions/1640389/pthreads-pthread-cond-signal-from-within-critical-section
http://stackoverflow.com/questions/6419117/signal-and-unlock-order
http://www.domaigne.com/blog/computing/condvars-signal-with-mutex-locked-or-not/#fig01