條件變量變量也是出自POSIX線程標准,另一種線程同步機制。主要用來等待某個條件的發生。可以用來同步同一進程中的各個線程。當然如果一個條件變量存放在多個進程共享的某個內存區中,那麼還可以通過條件變量來進行進程間的同步。
每個條件變量總是和一個互斥量相關聯,條件本身是由互斥量保護的,線程在改變條件狀態之間必須要鎖住互斥量。條件變量相對於互斥量最大的優點在於允許線程以無競爭的方式等待條件的發生。當一個線程獲得互斥鎖後,發現自己需要等待某個條件變為真,如果是這樣,該線程就可以等待在某個條件上,這樣就不需要通過輪詢的方式來判斷添加,大大節省了CPU時間。
在互斥量一文中說過:互斥量是用於上鎖,而不是用於等待;現在這句話可以加強為:互斥量是用於上鎖,條件變量用於等待;
條件變量聲明為pthread_cond_t數據類型,在<bits/pthreadtypes.h>中有具體的定義。
1條件變量初始化和銷毀
#include <pthread.h> int pthread_cond_init (pthread_cond_t *cond,const pthread_condattr_t *cond_attr) ; int pthread_cond_destroy (pthread_cond_t *cond); 兩者的返回值都是:若成功則返回0,否則返回錯誤號
上面兩個函數分別由於條件變量的初始化和銷毀。
和互斥量的初始化一樣,如果條件變量是靜態分配的,可以通過常量進行初始化,如下:
pthread_cond_t mlock = PTHREAD_COND_INITIALIZER;
也可以通過pthread_cond_init()進行初始化,對於動態分配的條件變量由於不能直接賦值進行初始化,就只能采用這種方式進行初始化。那麼當不在需要使用條件變量,釋放底層空間之前,需要調用pthread_cond_destroy()銷毀該條件所占用的資源。
2條件變量的使用
/* 等待條件變為真 */ 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); /* 喚醒一個等待條件的線程. */ int pthread_cond_signal (pthread_cond_t *cond); /* 喚醒等待該條件的所有線程 */ int pthread_cond_broadcast (pthread_cond_t *cond);
(1)pthread_cond_wait()函數用於等待條件被觸發。該函數傳入兩個參數,一個條件變量一個互斥量,函數將條件變量和互斥量進行關聯,互斥量對該條件進行保護,傳入的互斥量必須是已經鎖住的。調用pthread_cond_wait()函數後,會原子的執行以下兩個動作:
將調用線程放到等待條件的線程列表上,即進入睡眠;
對互斥量進行解鎖;
由於這兩個操作時原子操作,這樣就關閉了條件檢查和線程進入睡眠等待條件改變這兩個操作之間的時間通道,這樣就不會錯過任何條件的變化。
當pthread_cond_wait()返回後,互斥量會再次被鎖住。
(2)pthread_cond_timedwait()函數和pthread_cond_wait()的工作方式相似,只是多了一個等待時間。等待時間的結構為struct timespec:
struct timespec{ time_t tv_sec //Seconds. long tv_nsec //Nanoseconds. };
函數要求傳入的時間值是一個絕對值,不是相對值,例如,想要等待3分鐘,必須先獲得當前時間,然後加上3分鐘。
要想獲得當前系統時間的timespec值,沒有直接可調用的函數,需要通過調用gettimeofday函數獲取timeval結構,然後轉換成timespec結構,轉換公式就是:
timeSpec.tv_sec = timeVal.tv_sec; timeSpec.tv_nsec = timeVal.tv_usec * 1000;
所以要等待3分鐘,timespec時間結構的獲得應該如下所示:
struct timeval now; struct timespec until; gettimeofday(&now);//獲得系統當前時間 //把時間從timeval結構轉換成timespec結構 until.tv_sec = now.tv_sec; until.tv_nsec = now.tv_usec * 1000; //增加min until.tv_sec += 3 * 60;
如果時間到後,條件還沒有發生,那麼會返回ETIMEDOUT錯誤。
從pthread_cond_wait()和pthread_cond_timewait()成功返回時,線程需要重新計算條件,因為其他線程可能在運行過程中已經改變條件。
注意:pthread_cond_wait/pthread_cond_timewait函數的返回並不意味著條件的值一定發生了改變,必須重新檢查條件的值。
pthread_cond_wait/pthread_cond_timewait函數返回時,相應的互斥量將被當前線程鎖定。即使是函數出錯返回。
一般一個條件表達式都是在一個互斥量的保護下被檢查的。當條件表達式未被滿足時,線程將仍然阻塞在這個條件變量上。當另一個線程改變了條件的值並向條件變量發出信號時,等待在這個條件變量上的一個線程或者所有線程被喚醒,接著都試圖再次占有相應的互斥量。
阻塞在條件變量上的線程被喚醒以後,直到pthread_cond_wait函數返回之前,條件的值都有可能發生變化。所以函數返回以後,在鎖定相應的互斥量之前,必須重新測試條件變量。最後的測試方式是循環調用pthread_cond_wait函數,並把滿足條件的表達式置為循環的終止條件。例如:
pthread_mutex_lock(); while (condition_is_false) pthread_cond_wait(); pthread_mutex_unlock();
注意:pthread_cond_wait函數是退出點,如果在調用這個函數時,已有一個掛起的退出請求,且線程允許退出,這個線程將被終止並開始執行善後處理函數,而這時和條件變量相關的互斥量仍將處在鎖定狀態。(即pthread_cond_wait失敗返回,但仍然獲得互斥量,在清理函數注意解開該互斥量)
(3)pthread_cond_signal() & pthread_cond_broadcast()
這兩個函數都是用於向等待條件的線程發送喚醒信號,pthread_cond_signal()函數只會喚醒等待該條件的某個線程,pthread_cond_broadcast()會廣播條件狀態的改變,以喚醒等待該條件的所有線程。例如多個線程只讀共享資源,這是可以將它們都喚醒。
這裡要注意的是:一定要在改變條件狀態後,再給線程發送信號。
考慮條件變量信號單播發送和廣播發送的一種候選方式是堅持使用廣播發送。只有在等待者代碼編寫確切,只有一個等待者需要喚醒,且喚醒哪個線程無所謂,那麼此時為這種情況使用單播,所以其他情況下都必須使用廣播發送。
喚醒阻塞在條件變量上的所有線程的順序由調度策略決定,如果線程的調度策略是SCHED_OTHER類型的,系統將根據線程的優先級喚醒線程。
3喚醒丟失問題
在線程未獲得相應的互斥量時調用pthread_cond_signal或pthread_cond_broadcast函數可能會引起喚醒丟失問題。
喚醒丟失往往會在下面的情況下發生:
一個線程調用pthread_cond_signal或pthread_cond_broadcast函數;另一個線程正處在測試條件變量和調用pthread_cond_wait函數之間;沒有線程正在處在阻塞等待的狀態下。
示例代碼:
#include <stdio.h> #include <pthread.h> #include <unistd.h> pthread_mutex_t count_lock; pthread_cond_t count_nonzero; unsigned count = 0; void *decrement_count(void *arg) { pthread_mutex_lock(&count_lock); printf("decrement_count get count_lock\n"); while(count == 0) { printf("decrement_count count == 0 \n"); printf("decrement_count before cond_wait \n"); pthread_cond_wait(&count_nonzero, &count_lock); printf("decrement_count after cond_wait \n"); printf("decrement_count count = %d \n",count); } count = count + 1; pthread_mutex_unlock(&count_lock); } void *increment_count(void *arg) { pthread_mutex_lock(&count_lock); printf("increment_count get count_lock \n"); if(count == 0) { printf("increment_count before cond_signal \n"); pthread_cond_signal(&count_nonzero); printf("increment_count after cond_signal \n"); } count = count + 1; printf("huangcheng \n"); printf("increment_count count = %d \n",count); pthread_mutex_unlock(&count_lock); } int main(void) { pthread_t tid1, tid2; pthread_mutex_init(&count_lock, NULL); pthread_cond_init(&count_nonzero, NULL); pthread_create(&tid1, NULL, decrement_count, NULL); sleep(2); pthread_create(&tid2, NULL, increment_count, NULL); sleep(10); pthread_exit(0); return 0; }
查看本欄目更多精彩內容:http://www.bianceng.cn/OS/unix/
運行結果:
huangcheng@ubuntu:~$ ./a.out decrement_count get count_lock decrement_count count == 0 decrement_count before cond_wait increment_count get count_lock increment_count before cond_signal increment_count after cond_signal huangcheng increment_count count = 1 decrement_count after cond_wait decrement_count count = 1 huangcheng@ubuntu:~$
說明:
等待線程
1.使用pthread_cond_wait前要先加鎖
2.pthread_cond_wait內部會解鎖,然後等待條件變量被其它線程激活
3.pthread_cond_wait被激活後會再自動加鎖
激活線程:
1.加鎖(和等待線程用同一個鎖)
2.pthread_cond_signal發送信號
3.解鎖
激活線程的上面三個操作在運行時間上都在等待線程的pthread_cond_wait函數內部。
即執行順序:
等待線程->使用pthread_cond_wait前要先加鎖
等待線程->pthread_cond_wait內部會解鎖,然後等待條件變量被其它線程激活
激活線程->加鎖(和等待線程用同一個鎖)
激活線程->pthread_cond_signal發送信號
激活線程->解鎖
等待線程->pthread_cond_wait被激活後會再自動加鎖
作者信息:csdn博客 ctthuangcheng