深入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變量時。
例程:
- #include <pthread.h>
- #include "error.h"
- typedef struct my_struct_tag
- {
- pthread_mutex_t mutex;
- pthread_cond_t cond;
- int value;
- } my_struct_t;
- my_struct_t data = {PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER, 0};
- int main()
- {
- return 0;
- }
動態創建:一般情況下,條件變量要和它的判定條件定義在一起,此時若包含該條件變量的數據動態創建了,則條件變量也需要動態創建,不過記得不用時用pthread_cond_destroy銷燬。
例程:
- #include <pthread.h>
- #include "error.h"
- typedef struct my_struct_tag
- {
- pthread_mutex_t mutex;
- pthread_cond_t cond;
- int value;
- } my_struct_t;
- int main()
- {
- my_struct_t* data;
- data = (my_struct_t*)malloc(sizeof(my_struct_t));
- if(data == NULL)
- ERROR_ABORT(errno,"Allocate structure");
- int status;
- status = pthread_mutex_init(&data->mutex, NULL);
- if(status != 0)
- ERROR_ABORT(status, "Initial mutex");
- status = pthread_cond_init(&data->cond, NULL);
- if(status != 0)
- ERROR_ABORT(status, "Initial condition");
- /* .... */
- status = pthread_cond_destroy(&data->cond);
- if(status != 0)
- ERROR_ABORT(status, "Destroy cond");
- status = pthread_mutex_destroy(&data->mutex);
- if(status != 0)
- ERROR_ABORT(status, "Destroy mutex");
- free(data);
- return 0;
- }
等待條件變量
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一定是鎖定的。
多個條件變量可以共享一個互斥變量,相反則不成立。
例程:
- #include <pthread.h>
- #include <time.h>
- #include "error.h"
- #include <errno.h>
- typedef struct my_struct_tag
- {
- pthread_mutex_t mutex;
- pthread_cond_t cond;
- int value;
- } my_struct_t;
- my_struct_t data = { PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER, 0};
- int hibernation = 1;
- void* wait_thread(void* arg)
- {
- int status;
- sleep(hibernation);
- status = pthread_mutex_lock(&data.mutex);
- if(status != 0)
- ERROR_ABORT(status, "Lock mutex");
- data.value = 1;
- status = pthread_cond_signal(&data.cond);
- if(status != 0)
- ERROR_ABORT(status, "Singal cond");
- status = pthread_mutex_unlock(&data.mutex);
- if(status != 0)
- ERROR_ABORT(status, "Unlock mutex");
- return NULL;
- }
- int main(int argc, char* argv[])
- {
- pthread_t tid;
- int status;
- struct timespec timeout;
- if(argc > 1)
- hibernation = atoi(argv[1]);
- status = pthread_create(&tid, NULL, wait_thread, NULL);
- if(status != 0)
- ERROR_ABORT(status, "Create wait thread");
- timeout.tv_sec = time(NULL) + 2;
- timeout.tv_nsec = 0;
- status = pthread_mutex_lock(&data.mutex);
- if(status != 0)
- ERROR_ABORT(status, "Lock mutex");
- while(data.value == 0)
- {
- status = pthread_cond_timedwait(&data.cond, &data.mutex, &timeout);
- if(status == ETIMEDOUT)
- {
- printf("Condition wait timed out./n");
- break;
- }else
- if(status != 0)
- ERROR_ABORT(status, "timewait");
- }
- if(data.value != 0)
- printf("Condition wa signaled!/n");
- status = pthread_mutex_unlock(&data.mutex);
- if(status != 0)
- ERROR_ABORT(status, "Unlock mutex");
- }
喚醒等待條件變量的線程
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!)。
例程:
- #include "error.h"
- #include <pthread.h>
- #include <time.h>
- #include <string.h>
- #include <errno.h>
- typedef struct alarm_tag
- {
- struct alarm_tag* link;
- int seconds;
- time_t time;
- char message[64];
- } alarm_t;
- pthread_mutex_t alarm_mutex = PTHREAD_MUTEX_INITIALIZER;
- pthread_cond_t alarm_cond = PTHREAD_COND_INITIALIZER;
- alarm_t* alarm_list = NULL;
- time_t current_alarm = 0;
- /**
- * alarm_mutex need to be locked
- */
- void alarm_insert(alarm_t* alarm)
- {
- int status;
- alarm_t* next;
- alarm_t** last;
- last = &alarm_list;
- next = *last;
- while(next != NULL)
- {
- if(next->time >= alarm->time)
- {
- alarm->link = next;
- *last = alarm;
- break;
- }
- last = &next->link;
- next = next->link;
- }
- if(next == NULL){
- *last = alarm;
- alarm->link = NULL;
- }
- /*for test: output the list*/
- printf("[list: ");
- for(next = alarm_list; next != NULL; next = next->link)
- {
- printf("%d(%d)[/"%s/"] ", next->time, next->time-time(NULL), next->message);
- }
- printf("]/n");
- if(current_alarm ==0 || alarm->time < current_alarm)
- {
- current_alarm = alarm->time;
- status = pthread_cond_signal(&alarm_cond);
- if(status != 0)
- ERROR_ABORT(status,"Signal cond");
- }
- }
- void* alarm_thread(void* arg)
- {
- alarm_t* alarm;
- int sleep_time;
- time_t now;
- int status, expired;
- struct timespec cond_time;
- while(1)
- {
- status = pthread_mutex_lock(&alarm_mutex);
- if(status != 0)
- ERROR_ABORT(status, "lock");
- current_alarm = 0;
- while(alarm_list == NULL)
- {
- status = pthread_cond_wait(&alarm_cond, &alarm_mutex);
- if(status != 0 )
- ERROR_ABORT(status, "Wait cond");
- }
- alarm = alarm_list;
- alarm_list = alarm->link;
- now = time(NULL);
- expired = 0;
- if(alarm->time > now)
- {
- printf("[wating: %d(%d)/"%s/"]/n", alarm->time, alarm->time - time(NULL), alarm->message);
- cond_time.tv_sec = alarm->time;
- cond_time.tv_nsec = 0;
- current_alarm = alarm->time;
- while(current_alarm == alarm->time)
- {
- status = pthread_cond_timedwait(&alarm_cond, &alarm_mutex,&cond_time);
- if(status == ETIMEDOUT)
- {
- expired = 1;
- break;
- }
- }
- if(!expired)
- alarm_insert(alarm);
- }else
- expired = 1;
- if(expired)
- {
- printf("(%d) %s/n", alarm->seconds, alarm->message);
- free(alarm);
- }
- status = pthread_mutex_unlock(&alarm_mutex);
- if(status != 0)
- ERROR_ABORT(status, "Unlock mutex");
- }
- return 0;
- }
- int main()
- {
- pthread_t pid;
- int status;
- char line[128];
- status = pthread_create(&pid, NULL, alarm_thread, NULL);
- if(status != 0)
- ERROR_ABORT(status, "pthread_create");
- while(1)
- {
- fprintf(stdout, "Alarm>");
- fgets(line, sizeof(line), stdin);
- if(strlen(line) <= 0)
- continue;
- alarm_t* alarm = (alarm_t*)malloc(sizeof(alarm_t));
- if(alarm == NULL)
- ERROR_ABORT(errno,"memory can't allocated!");
- if(sscanf(line, "%d %s", &alarm->seconds, alarm->message) != 2)
- {
- printf("Bad Command/n");
- free(alarm);
- continue;
- }
- status = pthread_mutex_lock(&alarm_mutex);
- if(status != 0)
- ERROR_ABORT(status, "pthread mutex locking..");
- alarm->time = time(NULL) + alarm->seconds;
- /* insert into list*/
- alarm_insert(alarm);
- status = pthread_mutex_unlock(&alarm_mutex);
- if(status != 0)
- ERROR_ABORT(status, "pthread mutex unlocking...");
- }
- return 0;
- }