【操作系统】生产者消费者问题分析(线程同步)

来源:互联网 发布:java求100以内的素数 编辑:程序博客网 时间:2024/04/28 03:20

生产者消费者问题的介绍:生产者消费者模式,是一个多线程同步问题的经典案例。该问题描述了两个共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆就放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者。


基于以上的思路,我们可以先写出一个较为简单的算法。(此算法是不科学的,待改进。)

该算法使用了两个系统库函数,sleep 和 wakeup。调用 sleep 的线程会被阻断,直到有另一个线程用 wakeup 唤醒之。其中,itemCount用来表示实时下缓冲区的数据大小。

procedure producer() {    while (true) {        item = produceItem();        if (itemCount == BUFFER_SIZE) {            sleep();        }        putItemIntoBuffer(item);        itemCount = itemCount + 1;        if (itemCount == 1) {            wakeup(consumer);        }    }} procedure consumer() {    while (true) {        if (itemCount == 0) {            sleep();        }        item = removeItemFromBuffer();        itemCount = itemCount - 1;        if (itemCount == BUFFER_SIZE - 1) {            wakeup(producer);        }        consumeItem(item);    }}

上面代码中的问题在于它可能导致竞争条件,进而引发死锁。考虑下面的情形:

  1. 消费者把最后一个 itemCount 的内容读出来,注意它现在是零。消费者返回到while的起始处,现在进入 if 块;
  2. 就在调用sleep之前,CPU决定将时间让给生产者,于是消费者在执行 sleep 之前就被中断了,生产者开始执行;(死锁产生的开始)
  3. 生产者生产出一项数据后将其放入缓冲区,然后在 itemCount 上加 1;
  4. 由于缓冲区在上一步加 1 之前为空,生产者尝试唤醒消费者;
  5. 遗憾的是,消费者并没有在休眠,唤醒指令不起作用。当消费者恢复执行的时候,执行 sleep,一觉不醒。出现这种情况的原因在于,消费者只能被生产者在 itemCount 为 1 的情况下唤醒;
  6. 生产者不停地循环执行,直到缓冲区满,随后进入休眠。

由于两个进程都进入了永远的休眠,死锁情况出现了。因此,该算法是不完善的。


既然上一个算法我们通过一个线程判断一个常数(count)来睡眠与唤醒另一个线程的思路可能被系统的异常中断扼杀掉,那么我们可不可以通过本身调用某个函数,来实现本线程的唤醒与睡眠呢?下面我们给出基于信号量来实现唤醒睡眠的算法思路。

该方法(见下面的代码)使用了两个信号量,fillCount 和 emptyCount。

fillCount 用于记录缓冲区中将被读取的数据项数,emptyCount 用于记录缓冲区中空闲空间数。

当有新数据项被放入缓冲区时,fillCount 增加,emptyCount 减少。如果在生产者尝试减少 emptyCount 的时候发现其值为零,那么生产者就进入休眠。等到有数据项被消耗,emptyCount 增加的时候,生产者才被唤醒消费者的行为类似。(红字部分是关键,他保证了本线程的唤醒睡眠操作为自身操作,不用通过另一个线程来分配任务)


semaphore fillCount = 0; // 生产的项目semaphore emptyCount = BUFFER_SIZE; // 剩余空间 procedure producer() {    while (true) {        item = produceItem();        down(emptyCount);            putItemIntoBuffer(item);        up(fillCount);    }} procedure consumer() {    while (true) {        down(fillCount);            item = removeItemFromBuffer();        up(emptyCount);        consumeItem(item);    }}

生产者一直处于while循环中,当他发现信号量emptyCount>0时,便一直执行,当发现emptyCount=0时,便自动休眠,直到消费者修改了emptyCount变量从而唤醒他.


解决了死锁问题,我们能顺利的唤醒和睡眠生产者消费者线程,但是问题仍旧存在.如果仅仅是用于2个线程间的通信,那么上面的代码是没有问题的,但是当有多个生产者(或多个消费者)同时运行时,就会陷入例如生产者对同一地方写入相同数据的尴尬

  • 两个生产者都减少 emptyCount 的值;
  • 某一生产者寻找到下一个可用空槽;
  • 另一生产者也找到了下一个可用空槽,结果和上一步被找到的是同一个空槽;
  • 两个生产者向可用空槽写入数据。                  

     为了解决这个问题,需要在保证同一时刻只有一个生产者能够执行 putItemIntoBuffer()。为了实现这个思路,我们引入一个新的信号量,bool类型信号量mutex,一旦有一个生产者或消费者线程在执行,则执行前先把mutex置为0,禁止其他同类线程的执行。具体代码实现如下:

    semaphore mutex = 1;semaphore fillCount = 0;semaphore emptyCount = BUFFER_SIZE;  procedure producer() {    while (true) {        item = produceItem();        down(emptyCount);            down(mutex);                putItemIntoBuffer(item);            up(mutex);        up(fillCount);    }}procedure consumer() {    while (true) {        down(fillCount);            down(mutex);                item = removeItemFromBuffer();            up(mutex);        up(emptyCount);        consumeItem(item);    }}


    如上介绍了使用信号量来实现线程同步的方法,下面介绍另一种方法

    使用管程的算法

    管程的定义:

    一个管程定义了一个数据结构和能为并发进程所执行(在该数据结构上)的一组操作,这组操作能同步进程和改变管程中的数据。


    monitor ProducerConsumer {    int itemCount    condition full;    condition empty;     procedure add(item) {        while (itemCount == BUFFER_SIZE) {            wait(full);        }         putItemIntoBuffer(item);        itemCount = itemCount + 1;         if (itemCount == 1) {            notify(empty);        }    }     procedure remove() {        while (itemCount == 0) {            wait(empty);        }         item = removeItemFromBuffer();        itemCount = itemCount - 1;         if (itemCount == BUFFER_SIZE - 1) {            notify(full);        }         return item;    }} procedure producer() {    while (true) {        item = produceItem()        ProducerConsumer.add(item)    }} procedure consumer() {    while (true) {        item = ProducerConsumer.remove()        consumeItem(item)    }}


    本人由Cout_Sev 原创,部分资料参考网络及课本,转载注明出处。

    谢谢!


  • 0 0
    原创粉丝点击