通过生产者消费者模型来说明条件变量的作用:
对于生产者来说仓库必须要有空货架我才能往里面放东西,对于消费者来说仓库必须要有货物我才能消费。而且仓库每次只能进去一个人,所以需要互斥锁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 }
部分运行结果截图: