條件改變這兩個操作之間存在乙個時間視窗。這裡存在著競爭。
我們知道互斥量是可以用來解決上面的競爭問題的,所以條件變數本身 是由互斥量來保護的。
既然判斷和睡眠是由互斥量來保護從而成為乙個原子操作,那麼其他改變條件的執行緒就應該以一致的方式修改條件
也就是說其他執行緒在改變條件狀態前也必須首先鎖住互斥量。(如果修改操作不是用互斥量來保護的話,那麼判斷和休眠使用互斥量來保護也就沒有意義。因為其他執行緒還是可以在兩個操作的空隙中改變條件。但是如果修改操作也使用互斥量。因為判斷和休眠之前先加鎖了。那麼修改操作就只能等到判斷失敗和休眠兩個操作完成才能進行而不會在這之間修改)
下面是提供的介面:
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
pthread_mutex_t *restricr mutex,
const struct timespec *restrict timeout);
使用pthread_cond_wait等待條件變為真,傳遞給pthread_cond_wait的互斥量對條件變數進行保護,呼叫者把鎖住的互斥量傳遞給函式。
(互斥量在傳遞給函式之前已經呼叫pthread_mutex_lock鎖住)
函式把呼叫執行緒放到等待條件的執行緒列表上,然後對互斥量解鎖。這樣使得判斷和休眠成了原子操作。也就關閉了他們之間的
時間視窗。
當pthread_cond_wait返回時,會重新獲取互斥量(互斥量再次被鎖住)。
pthread_cond_timedwait與pthread_cond_wait的區別在於它指定了休眠的時間,如果時間到了,但是條件還是沒有出現,那麼pthread_wait_timedwait也將
重新獲取互斥量。然後返回 錯誤etimedout
需要注意的一點是。pthread_cond_timedwait的引數timeout不是相對值,而是絕對值。比如你想最多休眠三分鐘,那麼timeout不是3分鐘
而是當前時間加上3分鐘。
有兩個函式可以用來通知執行緒條件已滿足。pthread_cond_signal函式將喚醒等待該條件的某個執行緒。
pthread_cond_broadcast函式將喚醒等待該條件的所有執行緒。
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
現在我們來看乙個具體的例子。
在下面這個程式中兩個子執行緒在乙個條件變數cond上等待條件 i 等於一億成立。主線程中對 i 做自增操作,當i增加到一億的時候。條件成立那麼主線程 向條件變數傳送訊號。那麼兩個子執行緒就會從休眠中醒來從而繼續執行。
pthread_mutex_t mutex;
pthread_cond_t cond;
unsigned long i=0;
void *th1(void *arg)
void *th2(void *arg)
int main(void)
}pthread_join(t1,null);
pthread_join(t2,null);
pthread_cond_destroy(&cond);
pthread_mutex_destroy(&mutex);
exit(0);
}程式執行後停頓幾秒輸出:
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
也就是主線程中不停的 對i 進行自增操作。當i等於一億的時候,那麼條件滿足。主線程向條件變數傳送訊號。兩個在條件變數殺上等待
的子執行緒收到訊號後便開始執行。
但是 這個程式是存在問題的。這是我們要說的 第二個關於條件變數上的細節。
在兩個子執行緒中 我們只是簡單的使用了 pthread_cond_wait(&cond,&mutex);在條件變數上休眠等待主線程傳送訊號過來。
但是在pthread_cond_wait呼叫等待的時候,執行緒是釋放鎖的。(當他返回時才會再次獲得鎖)。
那麼就存在乙個問題
假想一下。當主線程傳送訊號過來後。在子執行緒 在pthread_cond_wait上等待發現訊號發過來了,那麼子執行緒將醒來並執行(注意這個時候pthread_cond_wait還未返回,那麼鎖是釋放的,因為pthread_cond_wait在等待是會釋放鎖,返回時才會重新獲得鎖),那麼如果這時候另乙個執行緒改變了 i(對i進行了增減操作。)
那麼此時i 不在是 一億。但是切換到子執行緒時他並不知情,他會仍舊認為條件是滿足的。也就是說 我們不應該僅僅依靠pthread_cond_wait的返回
就認為條件滿足。
所以 上面的程式 中 子執行緒中的 pthread_cond_wait(&cond,&mutex) 應該改為:
while(i!=100000000)
這樣即使 在子執行緒中 pthread_cond_wait返回前還未獲得鎖的這段空隙有其他執行緒改變了 i 使條件不在成立。那麼當pthread_cond_wait返回時他仍舊能發現 i 條件不成立。就會繼續呼叫pthread_cond_wait再條件變數上等待。
最後再來看個上面的乙個問題:
在給 在條件變數上等待的執行緒 傳送訊號的執行緒中有下面兩個步驟;
a:(1)對互斥量加鎖(pthread_mutex_lock)
(2)改變互斥量保護的條件。(對應上面的例子就是在主線程中的 i++ 操作)
(3)向等待條件的執行緒傳送訊號(pthread_cond_broadcast)
(4)對互斥量解鎖(pthread_mutex_unlock)
b:(1)對互斥量加鎖(pthread_mutex_lock)
(2)改變互斥量保護的條件。(對應上面的例子就是在主線程中的 i++ 操作)
(3)對互斥量解鎖(pthread_mutex_unlock)
(4)向等待條件的執行緒傳送訊號(pthread_cond_broadcast)
這兩種步驟其實都是可以的 但是都存在一些不足。
在 a 步驟中。 也就是主線程在傳送條件成立訊號在解鎖前。(上面給的例子是在解鎖後,在b中會說明)
那麼也就是主線程傳送訊號後還是持有鎖的,當子執行緒收到訊號後會結束休眠
但是前面說過pthread_cond_wait返回時會再次獲得鎖,但是主線程還並未釋放
鎖,所以會造成子執行緒收到訊號開始執行並立即阻塞。
在b步驟中。 主線程在釋放鎖後才傳送訊號。我們上面的例子就是這麼做的。但是這也存在乙個問題
但釋放鎖後,另乙個執行緒很可能會在傳送訊號之前獲得鎖並修改 變數i 導致條件再次不成立
但是會到主線程中他卻並不知情,導致仍會傳送訊號給子執行緒。子執行緒認為條件滿足
從休眠中醒來開始執行,但此時條件是不滿足的。
所以在上面的例子中我們將
pthread_cond_wait(&cond,&mutex) 改為:
while(i!=100000000)
讓子執行緒醒來後再次判斷條件是否成立。這樣就可以避免了上面的問題。
總結一下: 條件變數的要點在於 他提供了乙個讓多個執行緒匯合的點。但是條件變數本身是需要
互斥量來進行保護的。
我們不能僅僅根據pthread_cond_wait返回就認為條件滿足了。而需再次判斷條件是否正確
互斥鎖與條件變數
最近複習湯小丹的 計算機作業系統 西安電子科技大學出版社,第三版 程序 執行緒同步章節時,發現乙個疑問。在講程序同步時,提到了兩類方法 訊號量機制和管程機制。訊號量機制又包括四種 整型訊號量 記錄型訊號量 and型訊號量 訊號量集。如果採用整型訊號量或記錄型訊號量,則在共享多個資源時,可能出現程序死...
互斥鎖與條件變數
互斥鎖用於保護臨界區,使得任何時刻只有乙個執行緒在執行其中的 確切的說,互斥鎖用於保護多個執行緒或多個程序分享的共享資料。posix互斥鎖被宣告為具有pthread mutex t資料型別的變數。若互斥鎖變數是靜態分配的,則初始化為 static pthread mutex t lock pthre...
互斥鎖與條件變數
pthread cond wait總和乙個互斥鎖結合使用。在呼叫pthread cond wait前要先獲取鎖。pthread cond wait函式執行時先自動釋放指定的鎖,然後等待條件變數的變化。在函式呼叫返回之前,自動將指定的互斥量重新鎖住。int pthread cond signal pt...