執行緒同步 條件變數的使用細節分析

2021-07-24 08:23:55 字數 4445 閱讀 2021

如同互斥量和讀寫鎖一樣,條件變數也需要初始化和**

#include

int pthread_cond_init(pthread_cond_t *restrict cond,

pthread_condattr_t *restrict attr);

int pthread_cond_destroy(pthread_cond_t *cond);

互斥量和讀寫鎖解決了多執行緒訪問共享變數產生的競爭問題,那麼條件變數的作用何在呢。

條件變數的作用在於他給多個執行緒提供了乙個匯合的場所

。什麼意思呢?

舉個最簡單的例子,比如運動會賽跑中,所有選手都會等到發令槍響後才會跑,吧選手比作

其他的子執行緒。發令員比作主線程。 那麼就是說,所有的子執行緒現在都在等待主線程給予

乙個可以執行的訊號(發令槍響)。這就是這些子執行緒的匯合點。如果主線程沒給訊號,那麼子執行緒就會阻塞下去。

大概明白了 條件變數的作用,現在我們來考慮 第乙個使用細節上的問題

考慮乙個情況:b c d 三個執行緒都期望在乙個條件變數等待主線程傳送訊號,如果此時條件測試為假,那麼三個執行緒下一步應該是阻塞休眠。

但是在判斷條件不正確和休眠這之間有個時間視窗

,假如在bcd三個執行緒檢查條件為假後,cpu切換到另乙個執行緒a,

條件改變這兩個操作之間存在乙個時間視窗。這裡存在著競爭。

我們知道互斥量是可以用來解決上面的競爭問題的,所以條件變數本身 是由互斥量來保護的

。既然判斷和睡眠是由互斥量來保護從而成為乙個原子操作,那麼其他改變條件的執行緒就應該以一致的方式修改條件

也就是說其他執行緒在改變條件狀態前也必須首先鎖住互斥量

。(如果修改操作不是用互斥量來保護的話,那麼判斷和休眠使用互斥量來保護也就沒有意義。因為

其他執行緒還是可以在兩個操作的空隙中改變條件。但是如果修改操作也使用互斥量。因為判斷和休眠之前先加鎖了。那麼修改操作就只能等到判斷失敗和休眠兩個操作完成才能進行

而不會在這之間修改)

下面是提供的介面:

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返回就認為條件滿足了。而需再次判斷條件是否正確

執行緒同步 條件變數的使用細節分析

如同互斥量和讀寫鎖一樣,條件變數也需要初始化和 include int pthread cond init pthread cond t restrict cond,pthread condattr t restrict attr int pthread cond destroy pthread c...

執行緒同步 條件變數

當我們需要控制對記憶體資源的訪問的時候,可以用一種簡單的加鎖的方法來控制,即互斥鎖。但互斥鎖有乙個明顯的缺點,就是它只有兩個狀態 鎖定和非鎖定。而條件變數通過允許執行緒阻塞和等待另乙個執行緒傳送訊號的方法彌補來互斥鎖的不足。條件變數通常和互斥鎖一起使用。使用過程 1.呼叫pthread mutex ...

執行緒同步 條件變數

1.問題引入 互斥鎖問題,假設現在有兩個資源a和b,乙個執行緒先拿a再拿b,另乙個則相反,這樣導致的問題就是死鎖,即兩個執行緒無休止的互相等待 include include include include pthread mutex t g mtxa pthread mutex initializ...