生产者-消费者问题
生产者消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(英语:Bounded-buffer problem),是一个多线程同步问题的经典案例。该问题描述了两个共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。
要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆就放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者。通常采用进程间通信的方法解决该问题,常用的方法有信号灯法等。如果解决方法不够完善,则容易出现死锁的情况。出现死锁时,两个线程都会陷入休眠,等待对方唤醒自己。该问题也能被推广到多个生产者和消费者的情形。 —百度百科
问题分析
- 只有缓冲区没满时,生产者才能把产品放入缓冲区,否则必须等待。
- 只有缓冲区不空时,消费者才能从中取出产品,否则必须等待。
- 缓冲区是临界资源,各进程必须互斥地访问。
缓冲区临界资源,互斥访问,一个进程访问,其他进程必须等待,缓冲区满或空与生产者/消费者关系如图:
semaphore mutex = 1; //互斥信号量,缓冲区的互斥访问
semaphore empty = n; //同步信号量,表示空闲缓冲区的数量
semaphore full = 0; //同步信号量,表示产品的数量
//生产者
producer() {
while(1) {
生产一个产品;
P(empty);
P(mutex);
把产品放入缓冲区;
V(mutex);
V(full);
}
}
//消费者
consumer() {
while(1) {
P(full);
P(mutex);
从缓冲区取出一个产品;
V(mutex);
V(empty);
使用产品;
}
}
多生产者-多消费者问题
和之前的生产者-消费者问题不同,这里的多生产者和多消费者是指多种类型的生产者和消费者,而不是指多个生产者、消费者。不同的生产者会生产不同的产品放入缓冲区,不同的消费者从缓冲区取不同的产品。
不同生产者和不同消费者存在互斥关系。
例如:我们看一个多生产者-多消费者常见的问题
桌子上有一只盘子,每次只能向其中放入一个水果。爸爸专向盘子中放苹果,妈妈专向盘子中放橘子,儿子专等着吃盘子中的橘子,女儿专等着吃盘子中的苹果。只有盘子空时,爸爸或妈妈才可向盘子中放一个水果。仅当盘子中有自己需要的水果时,儿子或女儿可以从盘子中取出水果。
爸爸和妈妈是不同类型的生产者,爸爸只生产苹果,妈妈只生产橘子,儿子和女儿是不同类型的消费者,儿子只吃橘子,女儿只吃苹果。
问题分析
爸爸放入苹果后,女儿才能取苹果
妈妈放入橘子后,儿子才能取橘子
只有盘子为空时,爸爸或妈妈才能放入水果
semaphore mutex = 1; //实现互斥访问盘子(缓冲区)
semaphore apple = 0; //盘子中有几个苹果
semaphore orange = 0; //盘子中有几个橘子
semaphore plate = 1; //盘子中还可以放多少个水果
dad() {
while(1) {
准备一个苹果;
P(plate);
P(mutex);
把苹果放入盘子;
V(mutex);
V(apple);
}
}
mom() {
while(1) {
准备一个橘子;
P(plate);
P(mutex);
把橘子放入盘子;
V(mutex);
V(orange);
}
}
daughter() {
while(1) {
P(apple);
P(mutex);
从盘中取出苹果;
V(mutex);
V(plate);
吃掉苹果;
}
}
son() {
while(1) {
P(orange);
P(mutex);
从盘中取出橘子;
V(mutex);
V(plate);
吃掉橘子;
}
}
其实:可以不用设置互斥访问盘子的互斥变量mutex,盘子容量为1,任何时刻,apple、orange、plate 三个同步信号量中最多只有一个是1。
【参考文献】
https://www.bilibili.com/video/BV1YE411D7nH?from=search&seid=9416521726351939812