上一節中,Linux多線程之互斥鎖最後遺留了一個問題,consumewait函數會一直輪詢檢查生產者是否生產好了條目,這樣很浪費CPU的時間,因此,需要有另外一種類型的同步,它允許一個線程(或進程)睡眠到發生某個事件爲止。
互斥鎖用於上鎖,條件變量則用於等待。這兩種不同類型的同步都是需要的。條件變量是類型爲pthread_cond_t的變量,以下兩個函數使用了這些
#include <pthread.h>
int pthread_cond_wait( pthread_cond_t *cptr, pthread_mutex_t *mptr);
int pthread_cond_signal( pthread_cond_t *cptr)
每個條件變量總是有一個互斥鎖與之關聯。我們調用pthread_cond_wait等待某個條件爲真時,還會指定其條件變量的地址和所關聯的互斥鎖的地址。我們通過編寫上一節中的例子來解釋條件變量的使用。
#define MAXNITEMS 100000
#define MAXNTHREADS 100
int nitems ;
int buffer[MAXNITEMS];
struct
{
pthread_mutex_t mutex ;
int nput;
int nval;
} put = { PTHREAD_MUTEX_INITIALIZER };
struct
{
pthread_mutex_t mutex ;
pthread_cond_t cond ;
int nready ;//number ready for consumer
} ready = { PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER };
把互斥鎖變量mutex以及與之關聯的兩個變量nput和nval收集到一個名爲put的結構中,生產者使用這個結構。
把互斥鎖、條件變量以及計數器收集到一個名爲ready的結構中,消費者使用這個結構。
客戶端代碼沒有變動,produce和consume函數變動了。當生產者往數組buff放置一個新條目時,我們改用互斥鎖put.mutex來爲臨界區上鎖。在生產者生產完成之後,給用來統計準備好由消費者處理的條目數的計時器ready.nready加1。在加1之前,如果該計數器的值爲0,那就調用pthread_cond_signal喚醒可能正在等待其值變爲非零的任意線程。
void * produce( void* arg )
{
for( ; ; )
{
pthread_mutex_lock( &put.mutex );
if( put.nput > nitems )
{
pthread_mutex_unlock(&put.mutex );//array is full, return;
return NULL ;
}
buffer[put.nput] = put.nval;
put.nput++;
put.nval++;
pthread_mutex_unlock( &put.mutex );
pthread_mutex_lock( &ready.mutex );
if( ready.nready == 0 )
{
pthread_cond_signal( &ready.cond );
}
ready.nready ++ ;
pthread_mutex_unlock( &nready.mutex );
* ( (int*)arg ) += 1;
}
}
消費者只是等待計數器ready.nready變爲非零,既然該計數器是在所有的生產者和消費者之間共享的,那麼只有鎖住與之關聯的互斥鎖時才能測試它的值。如果在鎖住該互斥鎖期間計數器的值爲0,我們就調用pthread_cond_wait進入睡眠。該函數原子地執行以下兩個動作:
- 給互斥鎖ready.mutex解鎖;
- 把調用線程投入睡眠,直到另外某個線程就本身條件變量調用pthread_cond_signal。
void *comsume( void* arg )
{
int i ;
for( i =0 ;i < nitems; i ++ )
{
pthread_mutex_lock( &ready.mutex );
while( ready.nready == 0 )
{
pthread_cond_wait( &ready.cond, &ready.mutex );
}
ready.nready -- ;
pthread_mutex_unlock( &ready.mutex );
printf("buffer[%d] = %d\n", i, buffer[i] );
}
return NULL ;
}
之前的produce函數可能會引起上鎖衝突,pthread_cond_signal由當前鎖住某個互斥鎖的線程調用,而該互斥鎖是與本函數將給它發送信號的條件變量關聯的。我們可以假想最壞情況,當該條件變量被髮送信號後,系統立即調度等待其上的線程,該線程開始運行,但立即停止,因爲未解鎖。可把程序改成如下:
void * produce( void* arg )
{
for( ; ; )
{
pthread_mutex_lock( &put.mutex );
if( put.nput > nitems )
{
pthread_mutex_unlock(&put.mutex );//array is full, return;
return NULL ;
}
buffer[put.nput] = put.nval;
put.nput++;
put.nval++;
pthread_mutex_unlock( &put.mutex );
int dosignal = 0;
pthread_mutex_lock( &ready.mutex );
dosignal = ( ready.nready == 0 );
ready.nready ++ ;
pthread_mutex_unlock( &nready.mutex );
if( dosignal )
{
pthread_cond_signal( &ready.cond );
}
* ( (int*)arg ) += 1;
}
}
pthread_cond_signal只能喚醒等待在相應條件變量上的一個線程,在某些情況下一個線程認定有多個其它線程應被喚醒,這時可調用pthread_cond_broadcast喚醒阻塞在相應條件變量上的所有線程。堅持使用廣播,拒絕不知情的情況下使用單播。
pthread_cond_timedwait允許線程就阻塞時間設置一個限制值,abstime參數就是一個timespec結構。