1 -- 關於pthread條件變數
man pthread_cond_init | col -b > pthread_cond.man得到manual中的描述:A condition (short for ''condition variable'') is a synchronization device that allows threads to suspend execution and relinquish the processors until some predicate on shared data is satisfied. The basic operations on conditions are: signal the condition(when the predicate becomes true), and wait for the condition, suspending the thread execution until another thread signals the condition.
條件變數是同步線程的一種機制,它允許線程掛起,讓出處理器等待其他線程向它發送訊號,該線程收到該訊號後被喚醒繼續執行程式。對條件變數基本的操作就是:a)向條件變數發送訊號,喚醒等待的線程;b)等待條件變數並掛起直至其他線程向該條件變數發送訊號。為了防止競爭,條件變數總是和一個互斥鎖同時使用。2 -- 條件變數相關函數
phtread條件變數主要有如下的這些函數:
#include <pthread.h>pthread_cond_t cond = PTHREAD_COND_INITIALIZER;int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);int pthread_cond_signal(pthread_cond_t *cond);int pthread_cond_broadcast(pthread_cond_t *cond);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_destroy(pthread_cond_t *cond);
返回0為成功,非0為失敗。
注意:pthread_cond_init,pthread_cond_signal,pthread_cond_broadcast,pthread_cond_wait只返回0,不會返回其他錯誤碼。也就是說這幾個函數每次調用都會成功,編程時不用檢查傳回值。但pthread_cond_timedwait和pthread_cond_destroy會返回錯誤碼,需要注意!
3 -- 條件變數建立
條件變數的初始化有兩種方式:靜態和動態方式。
1.靜態方式
初始化方法:pthread_cond_t pcond = PTHREAD_COND_INITIALIZER;
對於靜態分配的條件變數,如果使用預設的條件變數屬性,可以直接使用PTHREAD_COND_INITIALIZER對條件變數進行賦值來初始化。pthread_cond_t是一個結構體,同樣PTHREAD_COND_INITIALIZER是一個結構體常量。
2.動態方式
動態方式調用pthread_cond_init函數對條件變數初始化,該函數的第二個參數指向條件變數屬性的結構體。儘管POSIX標準中為條件變數定義了屬性,但在LinuxThreads中沒有實現,因此cond_attr值通常設定為NULL。
4 -- 條件變數等待int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
pthread_cond_wait調用相當複雜,它是如下執行序列的一個組合:
(1)釋放互斥鎖 並且 線程掛起(這兩個操作是一個原子操作);
(2)線程獲得訊號,嘗試獲得互斥鎖後被喚醒;
我們知道調用pthread_cond_signal給條件變數發送訊號時,如果當時沒有線程在等待這個條件變數,訊號將被丟棄。如果"釋放互斥鎖"和"線程掛起"不是一個原子操作,那麼pthread_cond_wait線程在"釋放互斥鎖"和"線程掛起"之間,如果有訊號一旦發生,程式就會錯失一次條件變數變化。
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime)
返回錯誤碼:ETIMEDOUT。如果在指定時刻前,沒有訊號發生。
返回錯誤碼:EINTR。該函數被訊號中斷。
pthread_cond_timewait是等待條件變數的令一種形式,與前一種的區別是計時等待方式如果在給定時刻前條件沒有滿足,就返回ETIMEOUT結束等待。該函數在不同情況下會返回特定錯誤碼,編程時請參照開發。
5 -- 條件變數觸發int pthread_cond_signal(pthread_cond_t *cond);
pthread_cond_signal喚醒等待改條件變數所有線程中的一個。如果此時沒有線程在等待該條件變數,那麼就丟棄該訊號,當做什麼也沒發生。如果有多個線程在等待,精確保證只有一個線程被喚醒。
int pthread_cond_broadcast(pthread_cond_t *cond);
pthread_cond_broadcast喚醒等待該條件變數所有線程。如果此時沒有線程在等待該條件變數,那麼就丟棄該訊號,當做什麼也沒發生。
6 -- 條件變數銷毀int pthread_cond_destroy(pthread_cond_t *cond);
返回錯誤碼:EBUSY。當前還有線程在該條件變數上等待。
pthread_cond_destroy銷毀一個條件變數,但只有在沒有線程在該條件變數上等待的時候才能銷毀,否則返回EBUSY。因為Linux實現的條件變數沒有分配什麼資源,所以登出動作只包括檢查是否有等待線程。
7 -- memcached應用條件變數執行個體
背景:memcached是一個多線程結構的程式,主線程負責接收和分發請求,工作者線程實際處理請求。工作者線程在主線程中建立。建立線程後,工作者線程需要完成一些初始化工作,才允許主線程繼續執行,所以主線程需要等待這些工作者線程全部初始化完畢。
這裡就使用到了條件變數,大體的流程是這樣的:
(1)主線程初始化一個條件變數和一個互斥鎖;
(2)主線程建立n個工作者線程;
(3)主線程調用pthread_mutex_lock鎖定互斥鎖,然後調用pthread_cond_wait在條件變數上wait,等待被喚醒;
(4)子線程執行初始化代碼,完畢後擷取互斥鎖,累加已初始化線程數量,調用pthread_cond_signal給該條件變數發送訊號,同時釋放互斥鎖;
(5)線程調度喚醒主線程,主線程檢查現在已經初始化的線程數目,如果都初始化了就釋放互斥鎖,順序執行其他代碼;如果還沒初始化完畢,調用pthread_cond_wait再次等待。
主線程執行如下代碼:
/*主線程建立nthreads個線程,線程建立後進行初始化,初始化完畢後累加init_count*/for (i = 0; i < nthreads; i++) {create_worker(worker_libevent, &threads[i]);}/*主線程等待線程全部初始化,條件是已初始化量init_count等於線程數nthreads*/pthread_mutex_lock(&init_lock);while (init_count < nthreads) { pthread_cond_wait(&init_cond, &init_lock);}pthread_mutex_unlock(&init_lock);
工作者線程執行如下代碼:
//TODO:初始化代碼pthread_mutex_lock(&init_lock);init_count++;//累加已初始化線程數量pthread_cond_signal(&init_cond);pthread_mutex_unlock(&init_lock);
看完這段代碼之後,估計都會有一個疑問:流程(3)中互斥鎖已被主線程擷取了,線上程全部初始化完畢之前,主線程並沒有顯式釋放互斥鎖,為什麼在流程(4)中工作者線程還能擷取到互斥鎖呢?在講解pthread_cond_wait函數時說明過這個問題,也可以從看出其中奧妙。