Linux多線程之條件變量

上一節中,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進入睡眠。該函數原子地執行以下兩個動作:

  1. 給互斥鎖ready.mutex解鎖;
  2. 把調用線程投入睡眠,直到另外某個線程就本身條件變量調用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結構。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章