OSTEP 学习小记 -> 线程:消费者问题,条件变量与信号量

条件变量:

例子 生产者消费者问题:

#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里面。

不再赘述 可自行实验

书后还有哲学家就餐等等问题 自行看吧。

最好来一个信号量的实现,作结吧!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值