1. 程式人生 > >執行緒同步機制條件變數的使用與思考

執行緒同步機制條件變數的使用與思考

條件變數是Linux執行緒同步的一種機制,與互斥量一起使用時,允許執行緒以無競爭的方式等待特定條件的發生

關鍵函式

初始化與登出

#include <pthread.h>

// 靜態初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

// 動態初始化
int pthread_cond_init(thread_cond_t *cond, 
                      const pthread_condattr_t *attr);

// 反初始化,即登出
int pthread_cond_destroy(pthread_cond_t *cond);

返回值: 若成功,返回0
;否則,返回錯誤編碼

注意:
+ 只有在沒有執行緒在該條件變數上等待時,才可以登出條件變數,否則會返回EBUSY
+ Linux在實現條件變數時,並沒有為條件變數分配資源,所以在登出一個條件變數時,只需要注意該變數是否仍有等待執行緒即可

執行緒等待

#include <pthread.h>

int pthread_cond_wait(pthread_cond_t *cond, 
                      pthread_mutex_t *mutex);

int pthread_cond_timedwait(pthread_cond_t *cond
, pthread_mutex_t *mutex, const struct timespec *abstime); 返回值: 若成功,返回0;否則,返回錯誤編碼

執行過程如下:
1. 呼叫者把鎖住的互斥量傳給函式,然後函式自動把呼叫執行緒放到等待條件的執行緒列表上
2. 對互斥量進行解鎖,執行緒掛起進入等待(不佔用CPU時間) 
3. 函式被喚醒返回時,會自動對互斥量進行加鎖

pthread_cond_timedwait只是多了一個等待超時時間,通過timespec

指定,超時返回錯誤ETIMEDOUT

執行緒喚醒

#include <pthread.h>

int pthread_cond_signal(pthread_cond_t *cond);

int pthread_cond_broadcast(pthread_cond_t *cond);

返回值: 若成功,返回0;否則,返回錯誤編碼
  • pthread_cond_signal至少能喚醒一個等待該條件的執行緒
  • pthread_cond_broadcast則能喚醒等待該條件的所有執行緒
    需要注意的是,一定要在改變條件狀態以後再給執行緒發訊號

示例

示例程式碼可參考我的[github](https://github.com/AnSwErYWJ/DogFood/blob/24acbfdd45e80032c988ccd05e3b12f9cfe01849/C/thread/t_cond.c),由於篇幅原因,不在此貼出

一些思考

條件變數實質是什麼

條件變數實質是利用執行緒間共享的全域性變數進行同步的一種機制

互斥量保護的是什麼

示例中的相關程式碼
pthread_mutex_lock(&(test->mut));

while (test->condition == 0)
{
  pthread_cond_wait(&(test->cond), &(test->mut));
}

pthread_mutex_unlock(&(test->mut));
互斥量是用來保護條件`test->condition`在讀取時,它的值不被其它執行緒修改,如果條件成立,則此執行緒進入等待條件的執行緒佇列,對互斥量進行解鎖並開始等待

為什麼用while來判斷條件

如上面的程式碼所示,使用`while`對條件進行判斷的原因如下: 1. 若先解鎖互斥量,再喚醒等待執行緒,則條件可能被其它執行緒更改,使得等待條件再次成立,需要繼續等待 2. `pthread_cond_wait`可能存在意外返回的情況,則此時條件並沒有被更改,需要繼續等待。

造成意外返回的原因是Linux中帶阻塞功能的系統呼叫都會在程序收到signal後返回

先喚醒執行緒還是先解鎖

示例程式碼: 1. 情況一:先喚醒
pthread_mutex_lock(&(test->mut));
test->condition = 1
pthread_cond_signal(&(test->cond));
pthread_mutex_unlock(&(test->mut));
  1. 情況二:先解鎖
pthread_mutex_lock(&(test->mut));
test->condition = 1
pthread_mutex_unlock(&(test->mut));
pthread_cond_signal(&(test->cond));
兩種情況各有缺點: + 情況一在喚醒等待執行緒後,再解鎖,使得等待執行緒在被喚醒後試圖對互斥量進行加鎖時,互斥量還未解鎖,則執行緒又進入睡眠,待互斥量解鎖成功後,再次被喚醒並對互斥量加鎖,這樣就會發生兩次上下文切換,影響效能 + 情況二在喚醒等待執行緒前先解鎖,使得其它執行緒可能先於等待執行緒獲取互斥量,並對條件進行更改,使得條件變數失去作用

Reference

About me