【操作系统】生产者消费者问题分析(线程同步)
来源:互联网 发布: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); }}
上面代码中的问题在于它可能导致竞争条件,进而引发死锁。考虑下面的情形:
- 消费者把最后一个 itemCount 的内容读出来,注意它现在是零。消费者返回到while的起始处,现在进入 if 块;
- 就在调用sleep之前,CPU决定将时间让给生产者,于是消费者在执行 sleep 之前就被中断了,生产者开始执行;(死锁产生的开始)
- 生产者生产出一项数据后将其放入缓冲区,然后在 itemCount 上加 1;
- 由于缓冲区在上一步加 1 之前为空,生产者尝试唤醒消费者;
- 遗憾的是,消费者并没有在休眠,唤醒指令不起作用。当消费者恢复执行的时候,执行 sleep,一觉不醒。出现这种情况的原因在于,消费者只能被生产者在 itemCount 为 1 的情况下唤醒;
- 生产者不停地循环执行,直到缓冲区满,随后进入休眠。
由于两个进程都进入了永远的休眠,死锁情况出现了。因此,该算法是不完善的。
既然上一个算法我们通过一个线程判断一个常数(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个线程间的通信,那么上面的代码是没有问题的,但是当有多个生产者(或多个消费者)同时运行时,就会陷入例如生产者对同一地方写入相同数据的尴尬
为了解决这个问题,需要在保证同一时刻只有一个生产者能够执行 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 原创,部分资料参考网络及课本,转载注明出处。
谢谢!
- 【操作系统】生产者消费者问题分析(线程同步)
- 操作系统同步生产者消费者问题
- 线程同步生产者消费者问题
- 线程同步-生产者消费者问题
- Java线程同步实例 生产者 消费者问题分析
- 生产者消费者模式的线程同步问题
- 线程同步:生产者和消费者的问题
- 生产者消费者问题(线程同步)
- 经典线程同步问题(生产者&消费者)
- C#生产者与消费者问题 线程同步
- 线程同步-生产者与消费者问题
- [java线程同步]生产者消费者问题demo
- linux中的线程同步:生产者、消费者问题
- 线程同步之生产者-消费者问题
- 4线程同步-生产者消费者问题
- 生产者、消费者 线程同步
- 线程同步--生产者消费者
- 操作系统:生产者-消费者问题
- 临时1
- git暂存区的作用?
- 分支之间的切换。 想保存未提交到仓库的代码。?
- JQuery向<td>中插入<a>
- 操作sqlite数据库时需要考虑什么问题
- 【操作系统】生产者消费者问题分析(线程同步)
- sql去重问题(最近java笔试总遇到这个问题,忍不住要记录一下)
- android示例之闹钟
- jQuery Set Selected Option
- CF 295E Yaroslav and Points(Splay)
- 在线修改redo log
- ubuntu 下完全删除chrome
- 基于PhoneGap2.9框架的android插件的实现
- Spring data 多字段排序方法