Linux多线程之条件变量

上一节中,Linux多线程之互斥锁最后遗留了一个问题,consumewait函数会一直轮询检查生产者是否生产好了条目,这样很浪费CPU的时间,因此,需要有另外一种类型的同步,它允许一个线程(或进程)睡眠到发生某个事件为止。

互斥锁用于上锁,条件变量则用于等待。这两种不同类型的同步都是需要的。条件变量是类型为pthread_cond_t的变量,以下两个函数使用了这些

#include 
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唤醒阻塞在相应条件变量上的所有线程。坚持使用广播,拒绝不知情的情况下使用单播。

Linux多线程之条件变量_第1张图片

pthread_cond_timedwait允许线程就阻塞时间设置一个限制值,abstime参数就是一个timespec结构。

你可能感兴趣的:(Linux线程同步)