条件变量

条件变量本身不是锁!但它也可以造成线程阻塞。通常与互斥锁配合使用。给多线程提供一个会合的场所(共享的数据)。主要应用函数:

pthread_cond_init函数        pthread_cond_destroy函数

pthread_cond_wait函数       pthread_cond_timedwait函数

pthread_cond_signal函数      pthread_cond_broadcast函数

以上6 个函数的返回值都是:成功返回0, 失败直接返回错误号。

pthread_cond_t类型  用于定义条件变量    pthread_cond_t cond;

引入条件变量的目的:在使用互斥锁的基础上引入条件变量可以使程序的效率更高,因为条件变量的引入明显减少了线程取竞争互斥锁的次数。执行pthread_cond_wait或pthread_cond_timedwait函数的线程明显知道了条件不满足,要因此在其释放锁之后就没有必要再跟其它线程去竞争锁了,只需要阻塞等待signal或broadcast函数将其唤醒。这样提高了效率。

1pthread_cond_init函数

int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);

作用:初始化一个条件变量

参2:attr表条件变量属性,通常为默认值,传NULL即可。也可以使用静态初始化的方法,初始化条件变量:pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

2pthread_cond_destroy函数

int pthread_cond_destroy(pthread_cond_t *cond);

作用:销毁一个条件变量

3pthread_cond_wait函数

int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

作用:阻塞等待条件变量cond(形参1)满足条件,且释放已经掌握的互斥锁(解锁互斥量)相当于pthread_mutex_unlock(&mutex)(注意这是一个原子操作,即阻塞等待的同时马上解锁,类似sigsuspend函数);当被唤醒(signal或broadcast函数),pthread_cond_wait函数返回,解除阻塞并重新申请获取互斥锁:pthread_mutex_lock(&mutex);

4pthread_cond_timedwait函数

int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);

作用:与pthread_cond_wait函数作用相同,但其限时等待一个条件变量,即如果在规定的时间点(第三个形参)还未被唤醒时,该线程自动唤醒并解除阻塞重新申请获取互斥锁:pthread_mutex_lock(&mutex);

参数3struct timespec结构体。

struct timespec {

time_t tv_sec;               /* seconds */ 秒

long   tv_nsec;       /* nanosecondes*/ 纳秒

}                                                                                 

struct timespec结构体定义的是绝对时间(从1970年1月1日00:00:00开始计时的时间,unix操作系统的诞辰是1969年末)。time(NULL)返回的就是绝对时间(秒)。而alarm(1)是相对时间(相对于当前),相对当前时间定时1秒钟。

struct timespec t = {1, 0};  pthread_cond_timedwait (&cond, &mutex, &t); 只能定时到 1970年1月1日 00:00:01秒(早已经过去)。 1970年1月1日 00:00:00秒作为计时元年。正确用法:

time_t cur = time(NULL);   //获取当前时间(绝对时间)

struct timespec t;             //定义timespec 结构体变量t

t.tv_sec = cur+1;   //定时1秒

pthread_cond_timedwait (&cond, &mutex, &t); 传参   参考APUE.11.6线程同步条件变量小节

在讲解setitimer函数时我们还提到另外一种时间类型:

struct timeval {

time_t      tv_sec;   /* seconds */ 秒

suseconds_t tv_usec;     /* microseconds */ 微秒

};

5pthread_cond_signal函数

int pthread_cond_signal(pthread_cond_t *cond);

作用:唤醒至少一个阻塞在条件变量上的线程。

6pthread_cond_broadcast函数

int pthread_cond_broadcast(pthread_cond_t *cond);

作用:唤醒全部阻塞在条件变量上的线程

7)生产者消费者条件变量模型

线程同步典型的案例即为生产者消费者模型,而借助条件变量来实现这一模型,是比较常见的一种方法。假定有两个线程,一个模拟生产者行为,一个模拟消费者行为。两个线程同时操作一个共享资源(一般称之为汇聚),生产向其中添加产品,消费者从中消费掉产品。

看如下示例,使用条件变量模拟生产者、消费者问题:

//生产者消费者条件变量模型

#include 
#include 
#include 
#include 
#include 

struct production {
    int num;
    struct production *next;
};                  

struct production *head = NULL;  //定义全局指针head
struct production *rer = NULL;   //定义全局指针rer

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;  //静态初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;  //静态初始化

void *productor( void *arg )    //生产者
{
        srand( time( NULL ) );

        while(1){
            pthread_mutex_lock( &mutex );
            rer = (struct production *)malloc( sizeof(struct production) );
            rer->num = (rand( )%400 + 1);
            rer->next = head;
            head = rer;
            printf("--------The production is %d.\n",rer->num);
            pthread_mutex_unlock( &mutex );

            pthread_cond_signal( &cond );  //唤醒因wait阻塞的线程

            sleep( rand( )%3 );
        }


        return NULL;
}

void *consumer( void *arg )  //消费者
{
        srand( time( NULL ) );

        while(1){
            pthread_mutex_lock( &mutex );
            while( head == NULL )   //注意不能是if,必须采用循环
                pthread_cond_wait( &cond, &mutex );
            rer = head;
            head = rer->next;
            printf("++++++++The consumer is %d.\n",rer->num);
            pthread_mutex_unlock( &mutex );

            sleep( rand( )%3 );
        }

        return NULL;
}

int main( void )
{
        pthread_t pid, cid;
        int ret;

        ret = pthread_create( &pid, NULL, productor, NULL);
        if( ret != 0)
        {
            fprintf(stderr,"pthread_create error1: %s\n",strerror(ret));
            exit(1);
        }
        ret = pthread_create( &cid, NULL, consumer, NULL);
        if( ret != 0)
        {
            fprintf(stderr,"pthread_create error2: %s\n",strerror(ret));
            exit(1);
        }

        ret = pthread_join(pid,NULL);
        if( ret != 0)
        {
            fprintf(stderr,"pthread_join error1: %s\n",strerror(ret));
            exit(1);
        }

        ret = pthread_join(cid,NULL);
        if( ret != 0)
        {
            fprintf(stderr,"pthread_join error2: %s\n",strerror(ret));
            exit(1);
        }

        pthread_cond_destroy(&cond);         //销毁条件变量
        pthread_mutex_destroy(&mutex);       //销毁互斥锁

        return 0;
}

[root@localhost 02_pthread_sync_test]# ./pthrd_cond

--------The production is 257.

++++++++The consumer is 257.

--------The production is 324.

--------The production is 327.

++++++++The consumer is 327.

--------The production is 180.

--------The production is 313.

++++++++The consumer is 313.

--------The production is 285.

++++++++The consumer is 285.

++++++++The consumer is 180.

++++++++The consumer is 324.

--------The production is 21.

--------The production is 351.

++++++++The consumer is 351.

分析:

  1. 将生产者与消费者共享的资源(产品)用链表这种数据结构表示,head始终指向链表表头,rer用于向链表中增加或删除元素时的中间过渡。注意:这里的head与rer都是全局指针,都会被所有的消费者和生产者线程所共享,因此访问这两个资源时,都必须采用互斥锁,即先加锁,再访问,最后解锁。否则容易出现段错误。
  2. 这个程序只是创建了一个生产者线程和一个消费者线程,但是考虑的时候应该考虑有多个生产者线程和多个消费者线程。每一个线程都是采用互斥锁访问共享资源head和rer。通过采用条件变量把线程阻塞原因分为三部分:1.生产者因为加锁时(锁未解锁)而阻塞;2.消费者因为加锁时(锁未解锁)而阻塞;3.消费者因为执行了wait而阻塞(这是因为条件不满足)。这三部分线程,每当有锁解锁时,只有前两部分线程会去竞争,而第三部分线程只有等待被唤醒后才会去竞争。因此采用条件变量可以减少互斥锁的竞争次数。
  3. 上述程序中之所以必须采用while,是因为当因为wait阻塞而唤醒的线程,即使抢到了锁,公共资源也可能被其余消费者消耗了(因为生产者在signal之前会解锁,而解锁会引发消费者再次竞争锁),因此必须再次判断共享资源是否为空。
  4. 这道题目如果对共享资源的最大数量有限制,那么生产者线程也要引入条件变量。
  5. 相较于mutex而言,条件变量可以减少竞争。如直接使用mutex,除了生产者、消费者之间要竞争互斥量以外,消费者之间也需要竞争互斥量,但如果汇聚(链表)中没有数据,消费者之间竞争互斥锁是无意义的。有了条件变量机制以后,只有生产者完成生产,才会引起消费者之间的竞争。提高了程序效率。

你可能感兴趣的:(Linxu系统编程)