条件变量:
例子 生产者消费者问题:
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
// if(food<=0){
// printf("Consumer %s is out of food!I give signal!\n",now->name)
// ;
// pthread_cond_signal(&empty_cond);
// }
static int food=0;
pthread_mutex_t food_lock;
pthread_cond_t empty_cond;
pthread_cond_t food_cond;
typedef struct {
char name[100];
int loops;
}Tag;
void *producer(void *arg){
Tag*now=(Tag*)arg;
for(int i=0;i<now->loops;i++){
pthread_mutex_lock(&food_lock);
while(food>=3){
pthread_cond_wait(&empty_cond,&food_lock);
}
food+=1;
printf("Producer %s is producing.Now we have %d foods.\n",now->name,food);
pthread_cond_signal(&food_cond);
pthread_mutex_unlock(&food_lock);
}
}
void *consumer(void *arg){
Tag*now=(Tag*)arg;
for(int i=0;i<now->loops;i++){
pthread_mutex_lock(&food_lock);
while(food<=0){
pthread_cond_wait(&food_cond,&food_lock);
}
food-=1;
printf("Consumer %s is consuming.Now we have %d\n",now->name,food);
pthread_cond_signal(&empty_cond);
pthread_mutex_unlock(&food_lock);
sleep(1);
}
}
int main(){
pthread_t p1,c1;
Tag p1_tag={"P1",100},c1_tag={"C1",100};
pthread_create(&p1,NULL,producer,(void*)&p1_tag);
pthread_create(&c1,NULL,consumer,(void*)&c1_tag);
pthread_join(c1,NULL);
}
程序大致流程梳理:
首先 主线程等待c1 也就是消费者吃完所有。这里是100;
开始时两种情况,先执行P1,或者先执行C1(先执行P1的步骤类似,在此之分析P1). 先执行C1则由于food=0 调用wait ->转而执行P1。P1跳过i的一个循环,生产出3个food 同时唤醒consumer。关键点来了:释放锁 准备下一次生产,但这个生产能否进行呢? 不一定 要看Consumer是不是先于producer获取了锁?
仔细分析一下流程。
Producer 完成1次生产后
依次进行如下步骤:
1 printf("Producer %s is producing.Now we have %d foods.\n",now->name,food);
2 pthread_cond_signal(&food_cond);
3 pthread_mutex_unlock(&food_lock);
再看看 consumer的
1 while(food<=0){
2 pthread_cond_wait(&food_cond,&food_lock);
3 }
4 food-=1;
5 printf("Consumer %s is consuming.Now we have %d\n",now->name,food);
6 pthread_cond_signal(&empty_cond);
7 pthread_mutex_unlock(&food_lock);
8 sleep(1);
producer 执行1 的时候显然consumer还在睡眠,执行2的时候 consumer就被唤醒了:注意!wait的机理,执行wait的时候:
1.释放锁2.休眠,等待cond3.重新获取锁
也就是说 现在相当于consumer 在第3 步 也就是 相当于在执行lock(&food_lock)这一步! 那么我们的producer还拿着锁呢!所以consumer 此时被阻塞。。卡在4之前 。一直到producer 执行到3,释放锁了。OK 那么轮到consumer了! consumer消耗掉1个食物 。先停在这里!我们继续看producer。
pthread_mutex_lock(&food_lock);
while(food>=3){
pthread_cond_wait(&empty_cond,&food_lock);
}
food+=1;
printf("Producer %s is producing.Now we have %d foods.\n",now->name,food);
pthread_cond_signal(&food_cond);
pthread_mutex_unlock(&food_lock);
producer此时新开了一轮此的循环。然而他会开在第一步获取锁的那一步,因为锁在consumer手中!OK 那么等到consumer执行完毕到sleep(1)的时候,producer获取锁,food 不满足>=3继续生产!
于是 运行顺序应当是
生产1
消费1
生成1
消费1
也就是说 生产一个 消费一个!然而事实并非如此!
实际的一个运行顺序!
Producer P1 is producing.Now we have 1 foods.
Producer P1 is producing.Now we have 2 foods.
Producer P1 is producing.Now we have 3 foods.
Consumer C1 is consuming.Now we have 2
Producer P1 is producing.Now we have 3 foods.
Consumer C1 is consuming.Now we have 2
Producer P1 is producing.Now we have 3 foods.
Consumer C1 is consuming.Now we have 2
Producer P1 is producing.Now we have 3 foods.
Consumer C1 is consuming.Now we have 2
Producer P1 is producing.Now we have 3 foods.
Consumer C1 is consuming.Now we have 2
why? 为什么P1生产完一个 释放锁了之后,没有被consumer抢走???而是继续在下一轮执行?猜测:内部的调度问题。使得该线程获取的速度太快了,超过了传送的速度,验证我们的猜想,在
producer的末尾加上一个sleep。
Producer P1 is producing.Now we have 1 foods.
Consumer C1 is consuming.Now we have 0
这次看到了我们想要的!
此时有可能 consumer被唤醒,执行4 5 6 7 这里由于有个睡眠,估计他是强不到下个锁的,
那么按照我们的猜想,consumer
Key Point 分析:
为何要用while多次判断?能不能只用个if?不可以,在多线程的情况下,两个线程同时苏醒,一个强先消耗掉了food,一个试图访问0food 出错!
unlock 和 signal 顺序?可不可以相反:
根据linux环境编程手册说法,这个顺序是随意的。如果先unlock在signal->可能使得 signal过慢发送,导致出现我们 演示的情况,如果反了,也可能使得信号过快发送,使得另一个程序醒来又进入睡眠。
为何要加锁?
这是老生常谈的问题!保证不出现RACE条件!如果没加锁 可以有这样的情况:两个消费者同时键入改变food的语句,从而出现RACE竞争条件!
根据linux编程手册,往往条件变量+判断变量是一起的!
信号量
一种集合了上面变量+条件变量的方法!
sem_t s;
sem_initt(指向s的指针,描述信号量性质,计数器)
通过sem_wait() + sem_post。
sem_wait 逻辑:
1.计数器-1 如果计数器小于0 那就等待,
sem_post逻辑
计数器+1 并且唤醒对于等待的进程。
当信号值为负数时的绝对值!就是等待的进程量 (因为结束了就会post!加回去还)
信号量实现我们的锁!
只需要将锁 的计数器初始化为1即可,我们尝试在我们的消费者问题里这样做:
联联想一下 ->条件变量 中用了food界定何时发送信号,那么这里是不是一样呢?用信号量计数器作为界定!也就是while循环不需要了!直接拿一个信号量的wait 就自带判断数量!尝试实现!
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<semaphore.h>
static int food=0;
sem_t food_lock;
sem_t empty_sem;
sem_t full_sem;
typedef struct {
char name[100];
int loops;
}Tag;
void *producer(void *arg){
Tag*now=(Tag*)arg;
for(int i=0;i<now->loops;i++){
// sem_wait(&food_lock);
sem_wait(&empty_sem);//等到空了就进入
food+=1;
printf("Producer %s is producing.Now we have %d foods.\n",now->name,food);
sem_post(&full_sem);
// sem_post(&food_lock);
}
}
void *consumer(void *arg){
Tag*now=(Tag*)arg;
for(int i=0;i<now->loops;i++){
// sem_wait(&food_lock);
sem_wait(&full_sem);
food-=1;
printf("Consumer %s is consuming.Now we have %d\n",now->name,food);
sem_post(&empty_sem);
sleep(1);
}
}
int main(){
pthread_t p1,c1;
Tag p1_tag={"P1",100},c1_tag={"C1",10};
sem_init(&food_lock,0,1);
sem_init(&full_sem,0,0);
sem_init(&empty_sem,0,1);
pthread_create(&p1,NULL,producer,(void*)&p1_tag);
pthread_create(&c1,NULL,consumer,(void*)&c1_tag);
pthread_join(c1,NULL);
}
思路:full_sem代表满的信号,empty_sem代表用完的信号,生产者当然是等到空了在上,消费者则是满了再上。
按照语义,如果希望能够一次生产多个 应该调高empty的数量。然而调高之后会有问题。不妨假设先连续两次 生产,显然这是可以的,empty对应>=2就可以。在连续两次生产之间插入一次消费,好的 出现一个情况,生产者消费者!同时访问food!出错!为此 加一个锁就可以。
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<semaphore.h>
static int food=0;
sem_t food_lock;
sem_t empty_sem;
sem_t full_sem;
typedef struct {
char name[100];
int loops;
}Tag;
void *producer(void *arg){
Tag*now=(Tag*)arg;
for(int i=0;i<now->loops;i++){
sem_wait(&food_lock);
sem_wait(&empty_sem);//等到空了就进入
food+=1;
printf("Producer %s is producing.Now we have %d foods.\n",now->name,food);
fflush(stdout);
sem_post(&food_lock);
sem_post(&full_sem);
}
}
void *consumer(void *arg){
Tag*now=(Tag*)arg;
for(int i=0;i<now->loops;i++){
sem_wait(&food_lock);
sem_wait(&full_sem);
food-=1;
printf("Consumer %s is consuming.Now we have %d\n",now->name,food);
fflush(stdout);
sem_post(&food_lock);
sem_post(&empty_sem);
sleep(1);
}
}
int main(){
pthread_t p1,c1;
Tag p1_tag={"P1",100},c1_tag={"C1",10};
sem_init(&food_lock,0,1);
sem_init(&full_sem,0,0);
sem_init(&empty_sem,0,10);
pthread_create(&p1,NULL,producer,(void*)&p1_tag);
pthread_create(&c1,NULL,consumer,(void*)&c1_tag);
pthread_join(c1,NULL);
}
额。。。貌似卡住了?生产完10个不动了
Producer P1 is producing.Now we have 1 foods.
Producer P1 is producing.Now we have 2 foods.
Producer P1 is producing.Now we have 3 foods.
Producer P1 is producing.Now we have 4 foods.
Producer P1 is producing.Now we have 5 foods.
Consumer C1 is consuming.Now we have 4
Producer P1 is producing.Now we have 5 foods.
Producer P1 is producing.Now we have 6 foods.
Producer P1 is producing.Now we have 7 foods.
Producer P1 is producing.Now we have 8 foods.
Producer P1 is producing.Now we have 9 foods.
Producer P1 is producing.Now we have 10 foods.
分析一下该结果 当consumer第一次抢到结果的时候假定他恰好运行到sleep这一步,然后就轮到消费者了,生产者由于还有empty名额,会一直生产,然后。。直到empty耗尽,这样 生产者持有food锁,但是没有empty数据了,而消费者由于拿不到food锁被一直卡在外面。根据猜想,缩短sleep时间有可能会推迟这一情况(缩短消费者运行时间 不那么容易被抢占)。我们清0试试
Producer P1 is producing.Now we have 1 foods.
Producer P1 is producing.Now we have 2 foods.
Producer P1 is producing.Now we have 3 foods.
Producer P1 is producing.Now we have 4 foods.
Producer P1 is producing.Now we have 5 foods.
Producer P1 is producing.Now we have 6 foods.
Producer P1 is producing.Now we have 7 foods.
Producer P1 is producing.Now we have 8 foods.
Producer P1 is producing.Now we have 9 foods.
Producer P1 is producing.Now we have 10 foods.
这次直接出现了另一种情况,消费者根本就没有运行机会。。看来这是调度程序的关系。。
为了解决这一问题,缩小锁的作用域。就是把food锁调整到empty和full里面。
不再赘述 可自行实验
书后还有哲学家就餐等等问题 自行看吧。
最好来一个信号量的实现,作结吧!