Linux學習—條件變量實現同步

       與互斥鎖不同,條件變量是用來等待而不是用來上鎖的。條件變量用來自動阻塞一個線程,直到某特殊情況發生爲止。通常條件變量和互斥鎖同時使用。
       條件變量使我們可以睡眠等待某種條件出現。條件變量是利用線程間共享的全局變量進行同步的一種機制,主要包括兩個動作:一個線程等待"條件變量的條件成立"而掛起;另一個線程使"條件成立"(給出條件成立信號)。
       條件的檢測是在互斥鎖的保護下進行的。如果一個條件爲假,一個線程自動阻塞,並釋放等待狀態改變的互斥鎖。如果另一個線程改變了條件,它發信號給關聯的條件變量,喚醒一個或多個等待它的線程,重新獲得互斥鎖,重新評價條件。如果兩進程共享可讀寫的內存,條件變量可以被用來實現這兩進程間的線程同步。
       使用條件變量之前要先進行初始化。可以在單個語句中生成和初始化一個條件變量如:pthread_cond_t my_condition=PTHREAD_COND_INITIALIZER;(用於進程間線程的通信)。可以利用函數pthread_cond_init動態初始化。
       條件變量分爲兩部分: 條件和變量. 條件本身是由互斥量保護的. 線程在改變條件狀態前先要鎖住互斥量. 它利用線程間共享的全局變量進行同步的一種機制。

相關的函數如下:

1 int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);     
2 int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
3 int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);
4 int pthread_cond_destroy(pthread_cond_t *cond);  
5 int pthread_cond_signal(pthread_cond_t *cond);
6 int pthread_cond_broadcast(pthread_cond_t *cond);  //解除所有線程的阻塞

簡要說明:
(1)初始化.init()或者pthread_cond_t cond=PTHREAD_COND_INITIALIER;屬性置爲NULL
(2)等待條件成立.pthread_wait,pthread_timewait.wait()釋放鎖,並阻塞等待條件變量爲真
timewait()設置等待時間,仍未signal,返回ETIMEOUT(加鎖保證只有一個線程wait)
(3)激活條件變量:pthread_cond_signal,pthread_cond_broadcast(激活所有等待線程)
(4)清除條件變量:destroy;無線程等待,否則返回EBUSY

詳細說明
1.初始化:

條件變量採用的數據類型是pthread_cond_t, 在使用之前必須要進行初始化, 這包括兩種方式:

靜態: 可以把常量PTHREAD_COND_INITIALIZER給靜態分配的條件變量.

動態: pthread_cond_init函數, 是釋放動態條件變量的內存空間之前, 要用pthread_cond_destroy對其進行清理.

#include <pthread.h>

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

成功則返回0, 出錯則返回錯誤編號.

當pthread_cond_init的attr參數爲NULL時, 會創建一個默認屬性的條件變量; 非默認情況以後討論.

2.等待條件:

#include <pthread.h>

int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restric mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict timeout);

成功則返回0, 出錯則返回錯誤編號.

這兩個函數分別是阻塞等待和超時等待. 等待條件函數等待條件變爲真, 傳遞給pthread_cond_wait的互斥量對條件進行保護, 調用者把鎖住的互斥量傳遞給函數. 函數把調用線程放到等待條件的線程列表上, 然後對互斥量解鎖, 這兩個操作是原子的. 這樣便關閉了條件檢查和線程進入休眠狀態等待條件改變這兩個操作之間的時間通道, 這樣線程就不會錯過條件的任何變化.

當pthread_cond_wait返回時, 互斥量再次被鎖住.

3.通知條件:

#include <pthread.h>

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

成功則返回0, 出錯則返回錯誤編號.

這兩個函數用於通知線程條件已經滿足. 調用這兩個函數, 也稱向線程或條件發送信號. 必須注意, 一定要在改變條件狀態以後再給線程發送信號.

條件變量實現同步實例—控制方便麪的買賣

// pthread_cond_t cond;  定義一個條件變量
   14 // pthread_cond_init;   初始化條件變量
   15 // pthread_cond_destroy  銷燬
   16 // pthread_cond_wait    等待
   17 // pthread_cond_signal  通知  等待在條件變量 上的線程
   18 
   19 
   20 
   21 
   22 #include<stdio.h>
   23 #include<unistd.h>
   24 #include<pthread.h>
   25 #include<stdlib.h>
   26 
   27 int g_judge=0;               //0代表沒有方便麪  1代表有方便麪
   28 pthread_mutex_t mutex;
   29 pthread_cond_t full;
   30 pthread_cond_t empty;
   31 
   32 
W> 33 void* sale_noodles(void* arg)
   34 {
   35   
   36  while(1)
   37  {
   38     // usleep(300);
   39     
   40    pthread_mutex_lock(&mutex);                                                                                                                              
   41 
   42    while(g_judge==1)
    //先解鎖
   47      //集合了 解鎖+ 休眠+ 被喚醒後加鎖  的原子操作
   48      pthread_cond_wait(&full,&mutex);                        //這個 mutex 有什麼用?    --->   ++g_judge不是一個原子操作,涉及到對臨界資源的修改,多對多情況>      下不安全  
   49 
   50 
   51    }
   52      printf("product one noodle\n");
   53      g_judge++;
   54      //因爲買方便麪一直在wait死等,所以還需要通知一下
   55      pthread_cond_signal(&empty);
E> 56      pthread_mutex_unlock(mutex);
   57  
   58  }
   59 
   60 
   61      return NULL;
   62 
   63 }
   64 
   65 
   66 
E> 67 
   68 
   69 
   70 
E> 71 void* buy_noodles(void* arg)
   72 {                                                                                                                                                           
   73  
   74 
   75                                                           
   76 
   77 while(1)
{
pthread_mutex_lock(&mutex);                //這塊爲什麼加鎖,涉及到對臨界資源的訪問
   80   
   81   //如果沒有方便麪,就要等待
   82   while(g_judge==0)                     //必須得用 while 判斷  因爲有很多線程不停的判斷
   83    {
   84       //int pthread_cond_wait( ...  )
   85     pthread_cond_wait(&empty,&mutex);                //解鎖 和 掛起等待 必須是一個原子操作 ,否則不安全    &&  完成之後如果被喚醒會重新嘗試加鎖
   86   
   87    
   88    
   89    }
   90 
   91   
   92   printf("eat one noodle~~~\n");
   93   g_judge--;
   94   pthread_cond_signal(&full); 
   95   pthread_mutex_unlock(&mutex);
   96   
   97 }
   98 
   99 
  100   return NULL;
  101 
  102 }
  103 
         int main()
  115 {
  116  
  117   pthread_cond_init(&full,NULL); 
  118   pthread_cond_init(&empty,NULL); 
  119   pthread_mutex_init(&mutex,NULL);
  120   
  121   pthread_t tid1,tid2;
  122   pthread_create(&tid1,NULL,sale_noodles,NULL);
E>123   pthread_create(&tid2,NULL,buy_noodles,NULL);
  124 
  125   pthread_join(tid1,NULL);
  126   pthread_join(tid2,NULL);
  127 
  128   pthread_mutex_destroy(&mutex);
  129   pthread_cond_destroy(&full);
  130   pthread_cond_destroy(&empty);
  131 
  132 }

注意:我們有創建兩個條件變量empty和full。買賣雙方wait的時候分別在各自的條件變量上等待。如果買賣雙方都同時等在同一個條件變量上的話,在多線程的情況下會容易出現BUG;還有一點就是pthread_cond_wait爲什麼參數中還有mutex,是因爲它集合了 解鎖+ 休眠+ 被喚醒後加鎖 的原子操作。

//集合了 解鎖+ 休眠+ 被喚醒後加鎖  的原子操作
    pthread_cond_wait(&full,&mutex);   
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章