linux 條件變量

在多線程編程中僅使用互斥鎖來完成互斥是不夠用的, 如如下情形:

假設有兩個線程 t1 t2, 須要這個兩個線程循環對一個共享變量 sum 進行自增操做,那麼 t1 t2 只須要使用互斥量便可保證操做正確完成,線程執行代碼如所示:
編程

pthread_mutex_t sumlock= PTHREAD_MUTEX_INITIALIZER;
void * t1t2(void) {   pthread_mutex_lock(&sumlock);   sum++;   pthread_mutex_unlock(&sumlock); }

 

 若是這時須要增長另外一個線程 t3,須要 t3 count 大於 100 時將 count 值從新置 0 值,那麼能夠 t3 能夠實現以下:
多線程

void * t3 (void) {
  pthread_mutex_lock(&sumlock);
  if (sum >= 100) {
    sum = 0;
    pthread_mutex_unlock(&sumlock);
  } else {
    pthread_mutex_unlock(&sumlock);
    usleep(100);
  }
}

 

 以上代碼存在如下問題:函數

1) sum 在大多數狀況下小於 100, 那麼對 t3 的代碼來講,大多數狀況下, 走的是 else 分支, 只是 lock unlock,而後 sleep()。 這浪費了 CPU 處理時間。測試

2) 爲了節省 CPU 處理時間, t3 會在探測到 sum 沒到達 100 的時候 usleep()一段時間。這樣卻又帶來另一個問題, 亦即 t3 響應速度降低。 可能在 sum 到達 200 的時候, t3 纔會醒過來。 spa

這樣時間與效率出現了矛盾,而條件變量就是解決這個問題的好方法。 線程

建立與銷燬

1. 建立條件變量

Pthreads pthread_cond_t 類型的變量來表示條件變量。程序必須在使用 pthread_cond_t變量以前對其進行初始化。指針

(1) 靜態初始化

對於靜態分配的變量能夠簡單地將 PTHREAD_COND_INITIALIZER 賦值給變量來初始化默認行爲的條件變量。rest

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

 

2)動態初始化

對動態分配或者不使用默認屬性的條件變量來講可使用 pthread _cond_init()來初始化。函數原型以下:
code

int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);

 

參數 cond 是一個指向須要初始化 pthread_cond_t 變量的指針,參數 attr 傳遞 NULL 值時, pthread_cond_init()cond 初始化爲默認屬性的條件變量。
blog

函數成功將返回 0;不然返回一個非 0 的錯誤碼。

靜態初始化程序一般比調用 pthread_cond_init()更有效,並且在任何線程開始執行以前,確保變量被執行一次。

如下代碼示例了條件變量的初始化。

pthread_cond_t cond;
int error;
if (error = pthread_cond_init(&cond, NULL));
  fprintf(stderr, "Failed to initialize cond : %s\n", strerror(error)); 

 


2. 銷燬條件變量

函數 pthread_cond_destroy()用來銷燬它參數所指出的條件變量,函數原型以下:

int pthread_cond_destroy(pthread_cond_t *cond);

函數成功調用返回 0,不然返回一個非 0 的錯誤碼。如下代碼演示瞭如何銷燬一個條件變量。

pthread_cond_t cond;
int error;
if (error = pthread_cond_destroy(&cond))
  fprintf(stderr, "Failed to destroy cond : %s\n", strerror(error));

 

 

等待與通知

等待 

條件變量是與條件測試一塊兒使用的,一般線程會對一個條件進行測試,若是條件不知足就會調用條件等待函數來等待條件知足。

條件等待函數有 pthread_cond_wait()pthread_cond_timedwait()和兩個,函數原型以下:

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 *restrict mutex, const struct timespec *restrict abstime);

 

pthread_cond_wait()函數在條件不知足時將一直等待, 而 pthread_cond_timedwait()將只等待一段時間。

參數 cond 是一個指向條件變量的指針,參數 mutex 是一個指向互斥量的指針,線程在調用前應該擁有這個互斥量,當線程要加入條件變量的等待隊列時,等待操做會使線程釋放這個互斥量。 pthread_timedwait()的第三個參數 abstime 一個指向返回時間的指針,若是條件變量通知信號沒有在此等待時間

以前出現,等待將超時退出, abstime 是個絕對時間,而不是時間間隔。

 

 以上函數成功調用返回 0,不然返回非 0 的錯誤碼,其中 pthread_cond_timedwait() 函數若是 abstime 指定的時間到期,錯誤碼爲 ETIMEOUT

如下代碼使得線程進入等待,直到收到通知而且知足 a 大於等於 b 的條件。

pthread_mutex_lock(&mutex)
while(a < b)
  pthread_cond_wait(&cond, &mutex)
pthread_mutex_unlock(&mutex)

 

 通知 

 當另外一個線程修改了某參數可能使得條件變量所關聯的條件變成真時,它應該通知一個或者多個等待在條件變量等待隊列中的線程。

條件通知函數有 pthread_cond_signal()pthread_cond_broadcast()函數,其中 pthread_cond_signal 函數能夠喚醒一個在條件變量等待隊列等待的線程,而 pthread_cond_broadcast函數能夠全部在條件變量等待隊列等待的線程。函數原型以下:

int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);

參數 cond 是一個指向條件變量的指針。函數成功返回 0,不然返回一個非 0 的錯誤碼。

線程範例

#include<stdio.h>
#include<string.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
pthread_t tid[3];
int sum = 0;
pthread_mutex_t sumlock = PTHREAD_MUTEX_INITIALIZER; /* 靜態初始化互斥量 */
pthread_cond_t cond_sum_ready = PTHREAD_COND_INITIALIZER; /* 靜態初始化條件變量 */

void * t1t2(void *arg) {   int i;   long id = (long)arg;   for (i = 0; i < 60; i++) {     pthread_mutex_lock(&sumlock); /* 使用互斥量保護臨界變量 */     sum++;     printf("t%ld: read sum value = %d\n", id + 1 , sum);     pthread_mutex_unlock(&sumlock);     if (sum >= 100)       pthread_cond_signal(&cond_sum_ready); /* 發送條件通知,喚醒等待線程 */   }   return NULL; }
void * t3(void *arg) {   pthread_mutex_lock(&sumlock);   while(sum < 100) /* 不知足條件將一直等待 */     pthread_cond_wait(&cond_sum_ready, &sumlock); /* 等待條件知足 */   sum = 0;   printf("t3: clear sum value\n");   pthread_mutex_unlock(&sumlock);   return NULL; }
int main(void) {   int err;   long i;   for (i = 0; i < 2; i++) {     err = pthread_create(&(tid[i]), NULL, &t1t2, (void *)i); /* 建立線程 1 線程 2 */     if (err != 0) {       printf("Can't create thread :[%s]", strerror(err));   } }   err = pthread_create(&(tid[2]), NULL, &t3, NULL); /* 建立線程 3 */   if (err != 0)     printf("Can't create thread :[%s]", strerror(err));   for (i = 0; i < 3; i++)     pthread_join(tid[i], NULL);   return 0; }

 

運行結果以下所示, sum 累加到 100 時發送條件通知,但程序結果中 sum 計算到 103 時, t3 才被調用,這是由於 signal wait 調用之間有間隙存在。

t1: read sum value = 1
t1: read sum value = 2
...
t2: read sum value = 100
t1: read sum value = 101
t1: read sum value = 102
t1: read sum value = 103
t3: clear sum value
t2: read sum value = 1
.......
t2: read sum value = 17
相關文章
相關標籤/搜索