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

通过生产者消费者模型来说明条件变量的作用:
对于生产者来说仓库必须要有空货架我才能往里面放东西,对于消费者来说仓库必须要有货物我才能消费。而且仓库每次只能进去一个人,所以需要互斥锁mutex来保证,但是每次生产者放东西都需要看有没有空货架,消费者每次去取东西都要看有没有货物,若没有空货架或货物,生产者或消费者就需要不停的看有没有空货架或者货物,或者阻塞一段时间再看,不论哪种方法都是比较耗费cpu资源,但是如果换成通知的方式就提高了cpu的效率。例如仓库没有货物,消费者阻塞在仓库外面,当生产者把产品放到仓库中,然后通知消费者说:“我生产好了,你过来消费吧!”,反之对生产者亦然。所以这就是引入条件变量的原因,当共享资源不满足某条件时,就阻塞等待在该条件对应的条件变量上,若条件满足,则通知阻塞在条件变量上的线程,将其唤醒。

条件变量往往要结合互斥锁mutex使用,因为条件变量对应的是是否满足条件使用的,而这个条件描述的是共享资源的状态,它也是一个共享资源,所以需要互斥锁mutex,在使用条件变量前后需要加锁和解锁,避免发生错误,但是需要注意的是当不满足条件时线程会阻塞在该条件变量上,但是因为它之前加锁了,阻塞后无法解锁,这样就会造成死锁,所以我们需要让阻塞和解锁同时完成,也就是为一次原子操作。

线程库为我们提供的int pthread_cond_wait (pthread_cond_t * restrict cond , pthread_mutex_t * restrict mutex ) ;函数完美解决了上述需求,第一个参数为条件变量,第二个参数为互斥锁。并且使用前需要有准备工作:
1.创建互斥锁,并将其初始化。
2.创建条件变量并初始化
3.调用该函数前要加锁

原因上面已经说明。
其功能为:
1.将线程阻塞在条件变量cond上
2.对mutex解锁
3.被唤醒后重新申请加锁。

其中1和2为一次原子操作

唤醒阻塞在cond条件变量上的函数为:

// 向任意一个在等待的线程通知
int pthread_cond_signal ( pthread_cond_t *cond ) ;
// 通知所有在等待的线程
int pthread_cond_broadcast ( pthread_cond_t *cond ) ;

初始化和销毁函数为:

// 初始化条件变量
int pthread_cond_init (pthread_cond_t * restrict cond , pthread_condattr_t * restrict attr ) ;
// 销毁条件变量
int pthread_cond_destroy ( pthread_cond_t * cond ) ;

下面给出用条件变量实现生产者消费者模型:

有3个生产者,每个生产者循环17次生产后退出由主控线程回收,5个消费者循环11次消费后退出由主控线程回收,仓库最多容纳50个产品。

由于子线程会退出,所以会存在无消费者消费,仓库满后生产者阻塞等待消费者消费,产生死锁。或者无生产者生产,消费者阻塞等待生产者生产产品,产生死锁的现象,这样为了方便观察生产和消费情况

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<pthread.h>
  5 //仓库容量
  6 const int max = 50;
  7 //静态初始化生产者条件变量
  8 pthread_cond_t producer_cond = PTHREAD_COND_INITIALIZER;
  9 //静态初始化消费者条件变量
 10 pthread_cond_t consumer_cond = PTHREAD_COND_INITIALIZER;
 11 //静态初始化互斥锁
 12 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
 13 struct node{
 14     struct node *next;
 15     int num;
 16 };
 17 struct node *head = NULL;
 18 int node_num = 0;
 19 void *producer(void *arg)
 20 {
 21     int n = 17;
 22     long int i = (long int)arg;
 23     struct node *now=NULL;
 24     while(n--)
 25     {
 26         //模拟生产者生产产品
 27         now = (struct node*)malloc(sizeof(struct node));
 28         now->num = rand()%1000+1;
 29 
 30         pthread_mutex_lock(&mutex);//申请加锁
 31         while(node_num == max)//此处要用while而不能用if,因为有多个生产者,每次都要看下仓库是否有空闲货架
 32         {
 33             pthread_cond_wait(&producer_cond,&mutex);//若无空货物架,则阻塞在producer_cond条件变量上且解锁,注意此操作为原子操作
 34             //若被唤醒则申请加锁
 35         }
 36 
 37         //模拟放到仓库中
 38         now->next = head;
 39         head = now;
 40         node_num++;
 41 
 42         pthread_mutex_unlock(&mutex);//退出仓库解锁
 43         printf("-----------i'm %ld producer,i produce a %d product\n", i,now->num);
 44 
 45         pthread_cond_signal(&consumer_cond);//启动阻塞在consumer_cond条件变量上的某一个消费者线程
 46         usleep(rand()%1000);
 47     }
 48     pthread_exit(NULL);
 49 }
 50 void *consumer(void *arg)
 51 {
 52     int n = 11;
 53     long int i = (long int)arg;
 54     struct node *now=NULL;
 55     while(n--)
 56     {
 57         pthread_mutex_lock(&mutex);//申请加锁
 58         while(head == NULL)//每次都看一下是否有货物
 59         {
 60             pthread_cond_wait(&consumer_cond,&mutex);//若无货物则阻塞在consumer_cond条件变量上且解锁,为原子操作。
 61             //若被唤醒则申请加锁
 62         }
 63 
 64         //模拟消费者取走产品
 65         now = head;
 66         head = head->next;
 67         node_num--;
 68 
 69         pthread_mutex_unlock(&mutex);//出仓库解锁
 70 
 71         pthread_cond_signal(&producer_cond);//唤醒阻塞在producer_cond条件变量上的某一个生产者线程
 72 
 73         usleep(rand()%1000);
 74 
 75         printf("i'm %ld consumer,i consumption a %d product\n",i,now->num);//自己回去消费
 76         free(now);
 77     }
 78     pthread_exit(NULL);
 79 }
 80 int main(void)
 81 {
 82     srand(time(NULL));
 83     pthread_t tid[8];
 84     long int i;
 85 
 86     for(i=0; i < 3 ; i++)//创建3个生产者
 87         pthread_create(&tid[i],NULL,producer,(void*)(i+1));
 88     for(i=0; i < 5; i++)//创建5个消费者
 89         pthread_create(&tid[3+i],NULL,consumer,(void*)(i+1));
 90     for(i=0;i < 8; i++)//回收子线程
 91         pthread_join(tid[i],NULL);
 92 
 93     pthread_mutex_destroy(&mutex);
 94     pthread_cond_destroy(&producer_cond);
 95     pthread_cond_destroy(&consumer_cond);
 96 
 97     pthread_exit((void*)0);
 98 }

部分运行结果截图:
在这里插入图片描述

发布了31 篇原创文章 · 获赞 4 · 访问量 949

猜你喜欢

转载自blog.csdn.net/qq_39781096/article/details/104428426
今日推荐