深入Phtread(三):線程的同步-Condition Variables

深入Phtread(三):線程的同步-Condition Variables


    繼續昨天的線程同步,條件變量(Condition Variables)是用於線程間,通信共享數據狀態改變的機制。

簡介

    當線程互斥地訪問一些共享的狀態時,往往會有些線程需要等到這些狀態改變後才應該繼續執行。如:有一個共享的隊列,一個線程往隊列裏面插入數據,另一個線程從隊列中取數據,當隊列爲空的時候,後者應該等待隊列裏面有值才能取數據。而共享數據(隊列)應該用mutex來保護,爲了檢查共享數據的狀態(隊列是否爲空),線程必須先鎖定mutex,然後檢查,最後解鎖mutex。
 
    問題出來了:當另外一個線程B鎖定mutex後,往隊列裏面插入了一個值,B並不知道A在等着它往隊列裏面放入一個值。,線程A(等待狀態改變)一直在運行,線程B可能已經檢查過隊列是空的,並不知道隊列裏已經有值了,所以一直阻塞着自己。爲了解決這樣的問題引入了條件變量機制。線程B等待於一個條件變量,當線程A插入了一個值後,signal或broadcast這個條件變量,通知線程B狀態已改變,A發現條件變量被signaled了,就繼續執行。就這樣,當一個線程改變共享數據狀態後,可以及時通知那些等待於該狀態的線程。圖示下:
 

    中間的矩形代表條件變量,當線程線位於矩形內,表示線程等待該條件變量。位於中心線下下方,則表示signal了該條件變量。
    開始線程1 signal 了條件變量,由於沒有其他線程等待於該條件變量,所以沒什麼效果。然後,線程1和線程2先後等待該條件變量,過了一會,線程3 signal了條件變量,線程3的信號解除了線程1的阻塞。然後,線程3等待該條件變量。最後線程1 broadcast了該條件變量,同時解除了等待於條件變量的線程1和線程2。
 


條件變量的創建和銷燬

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int pthread_cond_init(pthread_cond_t* cond, pthread_condattr_t* condattr);
int pthread_cond_destroy(pthread_cond_t* cond);
 
和互斥量一樣,可以動態創建和靜態創建。
靜態創建:條件變量聲明爲extern或static變量時。
例程:
  1. #include <pthread.h>  
  2. #include "error.h"  
  3.   
  4. typedef struct my_struct_tag  
  5. {  
  6.     pthread_mutex_t mutex;  
  7.     pthread_cond_t cond;  
  8.     int value;  
  9. } my_struct_t;  
  10.   
  11. my_struct_t data = {PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER, 0};  
  12.   
  13. int main()  
  14. {  
  15.     return 0;  
  16. }  
 
動態創建:一般情況下,條件變量要和它的判定條件定義在一起,此時若包含該條件變量的數據動態創建了,則條件變量也需要動態創建,不過記得不用時用pthread_cond_destroy銷燬。
 
例程:
  1. #include <pthread.h>  
  2. #include "error.h"  
  3.   
  4. typedef struct my_struct_tag  
  5. {  
  6.     pthread_mutex_t mutex;  
  7.     pthread_cond_t cond;  
  8.     int value;  
  9. } my_struct_t;  
  10.   
  11. int main()  
  12. {  
  13.     my_struct_t* data;  
  14.     data = (my_struct_t*)malloc(sizeof(my_struct_t));  
  15.     if(data == NULL)  
  16.         ERROR_ABORT(errno,"Allocate structure");  
  17.   
  18.     int status;  
  19.     status = pthread_mutex_init(&data->mutex, NULL);  
  20.     if(status != 0)  
  21.         ERROR_ABORT(status, "Initial mutex");  
  22.     status = pthread_cond_init(&data->cond, NULL);  
  23.     if(status != 0)  
  24.         ERROR_ABORT(status, "Initial condition");  
  25.   
  26.     /* .... */  
  27.       
  28.     status = pthread_cond_destroy(&data->cond);  
  29.     if(status != 0)  
  30.         ERROR_ABORT(status, "Destroy cond");  
  31.     status = pthread_mutex_destroy(&data->mutex);  
  32.     if(status != 0)  
  33.         ERROR_ABORT(status, "Destroy mutex");  
  34.   
  35.     free(data);  
  36.   
  37.     return 0;  
  38. }  
 

等待條件變量

int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex);
int pthread_cond_timedwait(pthread_cond_t* cond, pthread_mutex_t* mutex, struct timespec* expiration);
 
    條件變量與互斥量一起使用,調用pthread_cond_wait或pthread_cond_timedwait時,記得在前面鎖定mutex,儘可能多的判斷判定條件。上面提到的兩個等待條件變量的函數,顯示解鎖mutex,然後阻塞線程等待狀態改變,等待的條件變量signaled後,鎖定mutex,返回。記着,這兩個函數返回時,mutex一定是鎖定的。
 
多個條件變量可以共享一個互斥變量,相反則不成立。
 
例程:
  1. #include <pthread.h>  
  2. #include <time.h>  
  3. #include "error.h"  
  4. #include <errno.h>  
  5.   
  6. typedef struct my_struct_tag  
  7. {  
  8.     pthread_mutex_t mutex;  
  9.     pthread_cond_t cond;  
  10.     int value;  
  11. } my_struct_t;  
  12.   
  13. my_struct_t data = { PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER, 0};  
  14.   
  15. int hibernation = 1;  
  16.   
  17. void* wait_thread(void* arg)  
  18. {  
  19.     int  status;  
  20.     sleep(hibernation);  
  21.   
  22.     status = pthread_mutex_lock(&data.mutex);  
  23.     if(status != 0)  
  24.         ERROR_ABORT(status, "Lock mutex");  
  25.   
  26.     data.value = 1;  
  27.     status = pthread_cond_signal(&data.cond);  
  28.     if(status != 0)  
  29.         ERROR_ABORT(status, "Singal cond");  
  30.   
  31.     status = pthread_mutex_unlock(&data.mutex);  
  32.     if(status != 0)  
  33.         ERROR_ABORT(status, "Unlock mutex");  
  34.   
  35.     return NULL;  
  36. }  
  37.   
  38. int main(int argc, char* argv[])  
  39. {  
  40.     pthread_t tid;  
  41.     int status;  
  42.     struct timespec timeout;  
  43.   
  44.     if(argc > 1)  
  45.         hibernation = atoi(argv[1]);  
  46.   
  47.     status = pthread_create(&tid, NULL, wait_thread, NULL);  
  48.     if(status != 0)  
  49.         ERROR_ABORT(status, "Create wait thread");  
  50.   
  51.     timeout.tv_sec = time(NULL) + 2;  
  52.     timeout.tv_nsec = 0;  
  53.   
  54.     status = pthread_mutex_lock(&data.mutex);  
  55.     if(status != 0)  
  56.         ERROR_ABORT(status, "Lock mutex");  
  57.   
  58.     while(data.value == 0)  
  59.     {  
  60.         status = pthread_cond_timedwait(&data.cond, &data.mutex, &timeout);  
  61.         if(status == ETIMEDOUT)  
  62.         {  
  63.             printf("Condition wait timed out./n");  
  64.             break;  
  65.         }else  
  66.         if(status != 0)  
  67.             ERROR_ABORT(status, "timewait");  
  68.     }  
  69.   
  70.     if(data.value != 0)  
  71.         printf("Condition wa signaled!/n");  
  72.   
  73.     status = pthread_mutex_unlock(&data.mutex);  
  74.     if(status != 0)  
  75.         ERROR_ABORT(status, "Unlock mutex");  
  76. }  
 
 

喚醒等待條件變量的線程

 
int pthread_cond_signal(pthread_cond_t* cond);
int pthread_cond_broadcast(pthread_cond_t* cond);
 
    一但有線程由於某些判定條件(predicate)沒滿足,等待條件變量。我們就有必要當條件滿足時,發送信號去喚醒這些線程。
    注意:broadcast通常很容易被認爲是signal的通用版,其實不能這樣理解,準確一點應該說,signal是broadcast的優化版。具體區別不大,但signal效率較broadcast高些。但你不確信有幾個線程等待條件變量時用broadcast(When in doubt, broadcast!)。
 
例程:

 

  1. #include "error.h"  
  2. #include <pthread.h>  
  3. #include <time.h>  
  4. #include <string.h>  
  5. #include <errno.h>  
  6.   
  7. typedef struct alarm_tag  
  8. {  
  9.     struct alarm_tag* link;  
  10.     int seconds;  
  11.     time_t time;  
  12.     char message[64];  
  13. } alarm_t;  
  14.   
  15. pthread_mutex_t alarm_mutex = PTHREAD_MUTEX_INITIALIZER;  
  16. pthread_cond_t alarm_cond = PTHREAD_COND_INITIALIZER;  
  17. alarm_t* alarm_list = NULL;  
  18. time_t current_alarm = 0;  
  19.   
  20. /** 
  21.  * alarm_mutex need to be locked   
  22.  */  
  23. void alarm_insert(alarm_t* alarm)  
  24. {  
  25.     int status;  
  26.   
  27.     alarm_t* next;  
  28.     alarm_t** last;  
  29.     last = &alarm_list;  
  30.     next = *last;  
  31.   
  32.     while(next != NULL)  
  33.     {  
  34.         if(next->time >= alarm->time)  
  35.         {  
  36.             alarm->link = next;  
  37.             *last = alarm;  
  38.             break;  
  39.         }  
  40.   
  41.         last = &next->link;  
  42.         next = next->link;  
  43.     }  
  44.   
  45.     if(next == NULL){  
  46.         *last = alarm;  
  47.         alarm->link = NULL;  
  48.     }  
  49.   
  50.     /*for test: output the list*/  
  51.     printf("[list: ");  
  52.     for(next = alarm_list; next != NULL; next = next->link)  
  53.     {  
  54.         printf("%d(%d)[/"%s/"]  ", next->time, next->time-time(NULL), next->message);  
  55.     }  
  56.     printf("]/n");  
  57.   
  58.     if(current_alarm ==0  || alarm->time < current_alarm)  
  59.     {  
  60.         current_alarm = alarm->time;  
  61.         status = pthread_cond_signal(&alarm_cond);  
  62.         if(status != 0)  
  63.             ERROR_ABORT(status,"Signal cond");  
  64.     }  
  65.   
  66. }  
  67.   
  68. void* alarm_thread(void* arg)  
  69. {  
  70.     alarm_t* alarm;  
  71.     int sleep_time;  
  72.     time_t now;  
  73.     int status, expired;  
  74.     struct timespec cond_time;  
  75.   
  76.     while(1)  
  77.     {  
  78.         status = pthread_mutex_lock(&alarm_mutex);  
  79.         if(status != 0)  
  80.             ERROR_ABORT(status, "lock");  
  81.   
  82.         current_alarm = 0;  
  83.   
  84.         while(alarm_list == NULL)  
  85.         {  
  86.             status = pthread_cond_wait(&alarm_cond, &alarm_mutex);  
  87.             if(status != 0 )  
  88.                 ERROR_ABORT(status, "Wait cond");  
  89.         }  
  90.   
  91.         alarm = alarm_list;  
  92.         alarm_list = alarm->link;  
  93.         now = time(NULL);  
  94.         expired = 0;  
  95.   
  96.         if(alarm->time > now)  
  97.         {  
  98.             printf("[wating: %d(%d)/"%s/"]/n", alarm->time, alarm->time - time(NULL), alarm->message);  
  99.   
  100.             cond_time.tv_sec = alarm->time;  
  101.             cond_time.tv_nsec = 0;  
  102.             current_alarm = alarm->time;  
  103.             while(current_alarm == alarm->time)  
  104.             {  
  105.                 status = pthread_cond_timedwait(&alarm_cond, &alarm_mutex,&cond_time);  
  106.                 if(status == ETIMEDOUT)  
  107.                 {  
  108.                     expired = 1;  
  109.                     break;  
  110.                 }  
  111.             }  
  112.   
  113.             if(!expired)  
  114.                 alarm_insert(alarm);  
  115.         }else  
  116.             expired = 1;  
  117.   
  118.         if(expired)  
  119.         {  
  120.             printf("(%d) %s/n", alarm->seconds, alarm->message);  
  121.             free(alarm);  
  122.         }  
  123.   
  124.         status = pthread_mutex_unlock(&alarm_mutex);  
  125.         if(status != 0)  
  126.             ERROR_ABORT(status, "Unlock mutex");  
  127.     }  
  128.   
  129.     return 0;  
  130. }  
  131.   
  132. int main()  
  133. {  
  134.     pthread_t pid;  
  135.     int status;  
  136.     char line[128];  
  137.   
  138.     status = pthread_create(&pid, NULL, alarm_thread, NULL);  
  139.     if(status != 0)  
  140.         ERROR_ABORT(status, "pthread_create");  
  141.   
  142.     while(1)  
  143.     {  
  144.         fprintf(stdout, "Alarm>");  
  145.         fgets(line, sizeof(line), stdin);  
  146.         if(strlen(line) <= 0)  
  147.             continue;  
  148.   
  149.         alarm_t* alarm = (alarm_t*)malloc(sizeof(alarm_t));  
  150.         if(alarm == NULL)  
  151.             ERROR_ABORT(errno,"memory can't allocated!");  
  152.   
  153.         if(sscanf(line, "%d %s", &alarm->seconds, alarm->message) != 2)  
  154.         {  
  155.             printf("Bad Command/n");  
  156.             free(alarm);  
  157.             continue;  
  158.         }  
  159.   
  160.         status = pthread_mutex_lock(&alarm_mutex);  
  161.         if(status != 0)  
  162.             ERROR_ABORT(status, "pthread mutex locking..");  
  163.   
  164.         alarm->time = time(NULL) + alarm->seconds;  
  165.   
  166.         /* insert into list*/  
  167.   
  168.         alarm_insert(alarm);  
  169.   
  170.         status = pthread_mutex_unlock(&alarm_mutex);  
  171.         if(status != 0)  
  172.             ERROR_ABORT(status, "pthread mutex unlocking...");  
  173.     }  
  174.   
  175.     return 0;  
  176. }  

發佈了8 篇原創文章 · 獲贊 5 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章