操作系统笔记(3):多线程信号量控制实现较复杂的生产者-消费者模型

在上一节已经讲解了信号量的基本原理,并用信号量实现了一个简单的锁变量机制,本节将使用信号量进行一个更为复杂系统的实现,对信号量的理解要求更高。
但首先要先明确,再怎么复杂它都是有核心概念的,就是:
当对某个信号量调用sem_wait(sem)时,只要sem为正数,就能继续进行运行,同时sem减一,否则阻塞在该瞬间。
而调用sem_post(sem)时,sem首先会加一,并能放行一个被阻塞的线程。

而其它的约束都是没有的,比如sem的初始值应该是多少啊,什么时候进行wp操作啊,wp需不需要成对出现啊。这些都是根据需要设定即可。

复杂的生产者-消费者模型:

我们设计这样的模型:有a个生产者、b个消费者,
生产者要花费一定时间生产一个产品,用一个值value表示,然后放进一个队列中。
消费者从队列中取出一个产品,花费一定时间消耗掉它。

这个队列就是该模型中的有界缓冲区。

接下来列出我们要在哪里、设计一个怎样的信号量才能实现所有共享数据的正确
产品值value,任意时刻只能有一个生产者访问到value并将至自增1(表示产生好一个产品,下一次生产value+1的产品)
队列头h,任意时刻只能有一个消费者访问h,取出队列中的头元素并将h后移
队列尾t,任意时刻只能有一个生产者访问t,放入队列中一个尾元素(生产者产出的产品value)
这里我们使用一个循环队列,那么问题来了,假设生产者运行生产飞快,消费者消费很慢,显然队列满的时候不可以放入。
同理消费者消费很快时,队列空时不可以继续消费。
因此这里要需要特殊信号量的设计:
canRead,控制型信号量,当队列非空时,canRead为一个正数,消费者要进行消费前,要先调用sem_wait(canRead),在canRead为非正数时说明此时队列中没有值可以取出消费,从而实现阻塞在这里,等待队列填充
canInput,控制型信号量,同样道理,保证队列满时,生产者会被阻塞起来,从而不往里放元素。

两者的初始值也需要进行仔细设计:
canRead,在一开始显然队列为空,所有的消费者都无法进行消费,可见一开始就得都阻塞起来,canRead都为0
canInput,在一开始显然能放入等于队列大小的元素,因此canInput为队列大小。
调用sempost的地方也很有意思,生产者成功地完整地放进去一个元素后,才可以让消费者读,所以sempost(canRead)在生产者放进去后再调用
canInput同样,是在消费者取出来后,才可以调用其sempost

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<semaphore.h>
#include<sys/select.h>
#include<sys/time.h>

#define F(i,a,b) for(int i=a;i<=b;i++)
#define MAXN 2
sem_t semValue,semTail,semHead,canInput,canRead;
int que[MAXN];
int h=0,t=0,value=1;

void msleep(int ms){
    struct timeval tval;
    tval.tv_sec=ms/1000;
    tval.tv_usec=(ms*1000)%1000000;
    select(0,NULL,NULL,NULL,&tval);
}

void provideData(){
    while(1){
        sem_wait(&semValue);
        int providedNum = value++;
        sem_post(&semValue);
        printf("now providing value = %d\n", providedNum);
        msleep(2000);
        sem_wait(&canInput);
        sem_wait(&semTail);
        que[t] = providedNum;
        t = (t + 1) % MAXN;
        sem_post(&semTail);
        sem_post(&canRead);
        printf("now provided and inputed value = %d\n", providedNum);
    }
}

void consumerData(){
    while(1){
        sem_wait(&canRead);
        sem_wait(&semHead);
        int consumedNum = que[h];
        h = (h + 1) % MAXN;
        sem_post(&semHead);
        sem_post(&canInput);
        printf("now consume value = %d\n", consumedNum);
        msleep(3000);
        printf("now has consumd value = %d\n", consumedNum);
    }
}

int main(){
    int a,b;
    printf("input the number of providers and consumers:\n");
    scanf("%d %d",&a,&b);
    pthread_t* provider, *consumer;
    provider = malloc(a * sizeof(pthread_t));
    consumer = malloc(b * sizeof(pthread_t));
    sem_init(&semValue, 0, 1);
    sem_init(&semHead, 0, 1);
    sem_init(&semTail, 0, 1);
    sem_init(&canRead, 0, 0);
    sem_init(&canInput, 0, MAXN);
    F(i,0,a-1){
        pthread_create(provider + i, NULL, (void *)provideData, NULL);
    }
    F(i,0,b-1){
    pthread_create(consumer + i, NULL, (void *)consumerData, NULL);
    }
    F(i,0,a-1){
        pthread_join(provider[i], NULL);
    }
    F(i,0,b-1){
        pthread_join(consumer[i], NULL);
    }
    sem_destroy(&semValue);
    sem_destroy(&semHead);
    sem_destroy(&semTail);
    sem_destroy(&canRead);
    sem_destroy(&canInput);
}

为了方便展示,这里设计有界缓冲区只有2个元素的大小

[root@localhost multithread]# ./main.out
input the number of providers and consumers:
3 3
now providing value = 1
now providing value = 2
now providing value = 3
now provided and inputed value = 3
now providing value = 4
now consume value = 3
now provided and inputed value = 2
now providing value = 5
now provided and inputed value = 1
now providing value = 6
now consume value = 1
now consume value = 2
now provided and inputed value = 4
now providing value = 7
now provided and inputed value = 5
now providing value = 8
now has consumd value = 3
now consume value = 4
now provided and inputed value = 6
now providing value = 9

这里我们安排3个生产者,3个消费者,
从输出中看到,先是3个生产者进行工作,然后放进去一个3号后,开始生产4,3随机立马被拿出去消费,2、1号顺势完成放进队列后,开始生产5、6,后1、2号再被消费。
这里value的自增也是正确的、生产、消费顺序也是合法的。
大家自己试试,把生产、消费的时间拉长动态观察分析分析,应该能更好地理解。

这里其实还有能继续要深入理解的地方,比如一开始我们可能所有的consumer都被canRead阻塞了,那么其实根本就不存在canRead大与0的时刻,但是在生产者生产后调用sempost(canRead),还能放行一个消费者立刻去读取,所以说canRead这个信号量在为负数的时候其绝对值才表示“阻塞”在其上的线程数。

因此前文的理解也还有发展空间,这里就是:阻塞在信号量上的线程会在sempost时放行一个,核心是维护信号量在为负数的时候其绝对值表示“阻塞”在其上的线程数

  • 6
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值