DJ2-5 生产者-消费者问题

目录

一、问题描述

二、问题分析

1. 实现数据存取

2. 实现互斥与同步

3. 执行流程

三、解决方法

1. 利用记录型信号量解决生产者-消费者问题

plus. 举例说明

2. 利用 AND 型信号量解决生产者-消费者问题


一、问题描述

有一群生产者进程在生产产品,并将这些产品提供给消费者进程去消费。为使生产者进程与消费者进程能并发执行,在两者之间设置了一个具有 n 个缓冲区的缓冲池,生产者进程将其所生产的产品放入一个缓冲区中;消费者进程可从一个缓冲区中取走产品去消费。


 

二、问题分析

1. 实现数据存取

使用数组作为缓冲池,容量为 n 。设置 in 和 out 两个指针,分别指向:

  • 生产者将要存放数据的存储单元
  • 消费者将要取出数据的存储单元

并且将数组组织成循环缓冲的形式。

/*指针但实际是数组下标*/
int in = 0;
int out = 0;

/*每次需要修改指针时*/
in = (in + 1) % n;
out = (out + 1) % n;

可将上述信息描述如下图:

2. 实现互斥与同步

根据实际需要,可以将信号量分为:

  • 互斥型信号量:S=1,即该资源只有一个,是临界资源
  • 资源型信号量:S≥1,即该资源不止一个

对于临界资源,我们必须保证对其访问的唯一性。因此需要将其的信号量初始值设置为 1,且将其置于临界区(critical section)中,即 wait() 和 signal() 操作之间。

在生产者-消费者问题中,我们设置三个信号量:

semaphore mutex, empty, full;

mutex 用于控制缓冲池的使用权,empty 用于指示缓冲池中空缓冲区的个数,full 用于指示缓冲池中满缓冲区的个数。空缓冲区就是未放入数据的存储单元,满缓冲区就是已放入数据的存储单元。个人认为,这两个名字取得好离谱,很容易将其与缓冲池的空和满混淆。

由于任一时刻,缓冲池的使用权只能被一个进程拥有,因此缓冲池信号量应该是一个互斥型信号量。由于空缓冲区和满缓冲区的数量可以是 0 ~ n 之间的任意值,因此它俩的信号量应该是资源型信号量。定义信号量如下:

semaphore mutex, empty, full;    
mutex.value = 1;       //由于缓冲池是一种临界资源,因此使用互斥型信号量
empty.value = n;       //代表空缓冲区的数量,最初存储单元均为空
full.value = 0;        //代表满缓冲区的数量,最初存储单元均为空

这两种信号量的区别就在于:

  • 初始值是否为 1
  • wait() 和 signal() 是否需要成对出现

互斥型信号量需要同时满足上述两个条件。

3. 执行流程

生产者-消费者执行流程图

三、解决方法

1. 利用记录型信号量解决生产者-消费者问题

变量定义、所需函数及主程序如下:

semaphore 变量使用 . 访问成员,semaphore 指针使用 -> 访问成员。

typedef struct {
    int value;
    struct process_control_block * list;
} semaphore;

int in = 0;
int out = 0;

semaphore mutex, empty, full;    
mutex.value = 1;       //由于缓冲池是一种临界资源,因此使用互斥型信号量
empty.value = n;       //代表空缓冲区的数量,最初存储单元均为空
full.value = 0;        //代表满缓冲区的数量,最初存储单元均为空

void producer();        //生产者进程
void consumer();        //消费者进程

void main() {
    cobegin     //并发执行
        producer(); consumer();
    coend
}

wait() 和 signal() 描述如下:

wait(semaphore * S) {
    S->value--;
    if(S->value < 0) block(S->list);
    //S->value == 0时代表刚好取走最后一个资源
}

signal(semaphore * S) {
    S->value++;
    if(S->value <= 0) wakeup(S->list);
    //S->value == 0时代表刚好还有一个进程被阻塞
}

生产者进程如下:

void producer() {
    do {

        //produce an item in nextp

        wait(&empty);    //判断是否有空存储单元
        wait(&mutex);    //判断是否可用缓冲池
        buffer[in] = nextp;
        in = (in + 1) % n;
        signal(&mutex);  //唤醒一个使用者
        signal(&full);   //唤醒一个消费者
    } while(true);
}

消费者进程如下:

void producer() {
    do {
        wait(&full);     //判断是否有空存储单元
        wait(&mutex);    //判断是否可用缓冲池
        nextc = buffer[out];
        out = (out + 1) % n;
        signal(&mutex);  //唤醒一个使用者
        signal(&empty);  //唤醒一个消费者

        //consume the item in nextc

    } while(true);
}

1)wait(&mutex) 必须放在 wait(&full) 后面,避免抢占到了缓冲池的使用权,结果发现没有空存储单元可用,从而导致资源浪费。

2)mutex 是互斥型信号量,因此对其的 wait 和 signal 操作必须成对出现。

3)进程一旦被阻塞,不管这个时间片是否用完,都立即进入下一个时间片。

plus. 举例说明

t1t2t3t4t5t6t7t8t9
runningc1: wait(full)

p1: item->nextp

p1: wait(empty)

p1: wait(mutex)

p2: item->nextp

p2: wait(empty)

p2: wait(mutex)

p3: item->nextp

p3: wait(empty)

p1: CS

p1: signal(mutex)

p1: signal(full)

finish

p2: CS

p2: signal(mutex)

p2: signal(full)

finish

c1: wait(mutex)

c1: CS

c1: signal(mutex)

c1: signal(empty)

finish

p3: wait(mutex)

p3: CS

p3: signal(mutex)

p3: signal(full)

finish

full.value0-1=-1(<0?)-1+1=0(<=0?)0+1=1(<=0?)1+1=2(<=0?)
full.list{c1}wakeup(full.list){}{}
empty.value2-1=1(<0?)1-1=0(<0?)0-1=-1(<0?)-1+1=0(<=0?)
empty.list{}{}{p3}wakeup(empty.list)
mutex.value1-1=0(<0?)0-1=-1(<0?)-1+1=0(<=0?)0+1=1(<=0?)1-1=0(<0?)0+1=1(<0?)

1-1=0(<0?)

0+1=0(<=0?)

mutex.list{}{p2}wakeup(mutex.list){}{}{}{}
ready{p1, p2, p3}{p2, p3, p1}{p3, p1}{p1}{p2, c1}{c1}{}{p3}{}

2. 利用 AND 型信号量解决生产者-消费者问题

变量定义如下:

int main() {
    // 定义变量
    typedef struct {
    int value;
    struct process_control_block * list;
    } semaphore;

    typedef struct {
        /* data */
    } item;

    int in = 0;
    int out = 0;
    item buffer[n];
    semaphore mutex, empty, full;
    mutex.value = 1;
    empty.value = n;

    //...
}

Swait() 和 Ssignal() 操作描述如下:

Swait(S1, S2, ... , Sn) {
    while(true) {
        if(S1 >= 1 && S2 >= 1 && ... && Sn >= 1) {
            for(i = 1; i <= n; ++i) Si = Si - 1;
            break;  //分配完资源后,直接退出while循环
        } else {
            /*place the process in the waiting queue associated with
            the first Si found with Si<1, and set the program count
            of this process to the beginning of Swait operation*/
        }
    }
}


Ssignal(S1, S2, ... , Sn) {
    for(i = 1; i <= n; ++i) Si = Si + 1;

    /*remove all the process waiting in the queue
    associated with Si into the ready queue*/
}

 生产者进程如下:

void producer() {
    do {
        //produce an item in nextp

        Swait(empty, mutex);    //此处讲究顺序
        buffer[in] = nextp;
        in = (in + 1) % n;
        Ssignal(mutex, full);   //此处讲究顺序
    } while(true);
}

消费者进程如下:

void consumer() {
    do {
        Swait(full, mutex);     //此处讲究顺序
        nextc = buffer[out];
        out = (out + 1) % n;
        Ssignal(mutex, empty);  //此处讲究顺序

        //consume the item in nextc
    } while(true);
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值