等待條件變量的正確姿勢:
void wait() { mutex.lock() while (wait_flag == false) { condition.wait() } do_something(); mutex.unlock(); }
1)必須使用while循環來等待條件變為真,即醒來之后要立馬再判斷一次條件是否成立再決定是否需要繼續等待,
因為很有可能條件並不為真,但是線程卻被各種奇怪的中斷或者pthread_cond_broadcast這樣的東西給喚醒了
2)至於condition.wait()的作用是提供一個原子操作:
進入condition.wait() 時,將 wait 和 mutex.unlock 兩步變成一個原子操作,確保在線程進入 wait 之前不會釋放鎖,也就是保證了在進入 wait 之前沒有人能夠改動 wait_flag (所有對 wait_flag的操作都要加鎖,否則條件變量沒有意義),
因為pthread_cond_signal這類函數只會喚醒已經在等待隊列里的線程,如果這兩步不是原子操作,那么在釋放鎖之后,進入等待隊列之前的這段時間里,有人調用了cond_signal之后也不會被喚醒
觸發條件的正確姿勢:
void notify() { mutex.lock(); push_resource(); condition.signal(); mutex.unlock(); } void notifyAll() { mutex.lock(); change_status(); condition.broadcast(); mutex.unlock(); }
通常在資源可用時使用signal,在改變狀態時使用broadcast。
這里特別說明一下網上流傳的阻塞隊列 (為簡單起見,這里假設隊列最大長度為無限長) 的實現有個錯誤:
void push(e) { mutex.lock(); queue.push(e); if (queue.size() == 1) { //錯誤,應該把if條件去掉 cond.notify(); //或者不去掉if,改成notifyAll } mutex.unlock(); }
因為pthread_cond_signal只是喚醒線程到就緒隊列,至於這個線程能不能搶到鎖以及搶到鎖之后能不能保證立馬被調度並從隊列里取元素就不保證了。
所以會出現這種情況,有多個飢餓的線程在等待隊列變成非空,這個時候有一個線程放進去一個元素,之后喚醒了一個等待線程到就緒隊列,但是他卻沒有獲取到鎖,
這個時候其他的線程往隊列里push的時候,由於消費者線程沒有來得及取元素,所以隊列元素的總量大於1,所以並不觸發signal。。。
這就導致在下次隊列變為空之前,可能只有一個消費者線程會被喚醒。
使用notifyAll可以避免這個問題,但是代價是虛假喚醒(據說對這種情況會優化,這里不討論。。恩,因為我特么也是不懂啊,記住正確的做法不就好了么)
所以對於有多個消費者線程的阻塞隊列,正確的寫法是每次push都進行一次notify(),而不是隊列為空時才notify