计算机操作系统17
第十七课:生产者-消费者问题(Producer-Consumer Problem)
一、什么是生产者-消费者?
先来看一个生活例子。
假设:
有一家奶茶店。
员工:
负责:
做奶茶。
顾客:
负责:
拿奶茶。
中间:
有一个:
奶茶架。
员工(生产者)
↓
【奶茶架】
↓
顾客(消费者)
员工:
一直:
做奶茶。
顾客:
一直:
拿奶茶。
是不是:
很合理?
二、为什么会出问题?
假设:
奶茶架:
只能:
放:
3 杯奶茶。
开始:
[ ][ ][ ]
员工:
一直做。
[奶][ ][ ]
继续:
[奶][奶][ ]
继续:
[奶][奶][奶]
已经:
满了。
如果:
员工:
还继续:
放。
是不是:
放不下?
所以:
必须:
等待。
另一边。
如果:
奶茶架:
空了。
[ ][ ][ ]
顾客:
还来拿。
是不是:
拿不到?
所以:
消费者:
也必须:
等待。
三、操作系统里的对应关系
奶茶店:
对应:
程序。
| 奶茶店 | 操作系统 |
|---|---|
| 员工 | 生产者线程 |
| 顾客 | 消费者线程 |
| 奶茶架 | 缓冲区(Buffer) |
| 奶茶 | 数据 |
缓冲区:
就是:
一块:
共享内存。
四、缓冲区会出现两个问题
假设:
缓冲区:
大小:
3
现在:
已经:
满了。
生产者:
还能:
放吗?
不能。
必须:
等待。
另一种。
缓冲区:
为空。
消费者:
还能:
取吗?
不能。
必须:
等待。
所以:
这里:
需要:
同步。
另外。
如果:
两个生产者:
同时:
往:
同一个位置:
写数据。
是不是:
又会:
覆盖?
两个消费者:
同时:
取:
同一个数据。
是不是:
又会:
重复?
所以:
还需要:
互斥。
一个总结
生产者-消费者问题里,同时存在两类需求:
- 同步 :控制"什么时候能生产、什么时候能消费"。
- 互斥 :保证同一时刻只有一个线程修改缓冲区。
很多初学者只想到同步,忘了互斥,这是最容易丢分的地方。
五、需要几个信号量?
这是考试:
第一问。
答案:
三个。
① mutex
作用:
保护:
缓冲区。
初值:
1
因为:
一次:
只能:
一个线程:
操作。
② empty
表示:
空位置。
例如:
缓冲区:
大小:
5。
开始:
empty = 5
说明:
还有:
5 个位置。
③ full
表示:
已有数据。
开始:
full = 0
因为:
一开始:
没有:
任何数据。
六、三个信号量总结
| 信号量 | 初值 | 含义 |
|---|---|---|
| mutex | 1 | 互斥访问缓冲区 |
| empty | n(缓冲区大小) | 空缓冲区数量 |
| full | 0 | 已放入数据数量 |
这是考试必背。
七、生产者应该怎么写?
假设:
生产:
一个数据。
第一步:
是不是:
先看:
有没有:
空位置?
所以:
第一句:
P(empty);
意思:
申请:
一个空位置。
第二步。
是不是:
还要:
锁门?
因为:
别人:
不能:
同时:
改缓冲区。
于是:
P(mutex);
第三步。
真正:
放数据。
put();
第四步。
放完。
是不是:
解锁?
V(mutex);
第五步。
缓冲区:
多了:
一个数据。
消费者:
可以:
拿了。
于是:
V(full);
完整代码:
P(empty);
P(mutex);
放入数据;
V(mutex);
V(full);
八、消费者怎么写?
第一步。
有没有:
数据?
所以:
先:
P(full);
没有数据:
就等待。
第二步。
锁门。
P(mutex);
第三步。
真正:
拿数据。
get();
第四步。
解锁。
V(mutex);
第五步。
空位置:
多了:
一个。
于是:
V(empty);
完整:
P(full);
P(mutex);
取数据;
V(mutex);
V(empty);
九、为什么顺序不能写反?
来看一个经典错误。
有人:
写:
P(mutex);
P(empty);
这样:
可以吗?
不可以。
为什么?
假设:
缓冲区:
已经:
满了。
生产者:
执行:
P(mutex)
拿到了:
锁。
接着:
执行:
P(empty)
发现:
没有:
空位。
于是:
睡觉。
注意!
它:
睡觉的时候:
还拿着:
锁。
消费者:
想:
拿数据。
第一句:
P(full)
成功。
第二句:
P(mutex)
发现:
锁:
没了。
消费者:
也睡觉。
结果:
生产者:
等消费者
↓
消费者:
等生产者
是不是:
谁也:
醒不过来?
这就是:
死锁(Deadlock)
十、为什么正确顺序不会死锁?
正确:
一定:
先:
P(empty)
资源:
够。
再:
P(mutex)
所以:
不会:
拿着:
锁:
睡觉。
这一点:
考试:
经常:
考。
十一、一张图理解
生产者
P(empty)
P(mutex)
放入数据
V(mutex)
V(full)
-----------------------
消费者
P(full)
P(mutex)
取出数据
V(mutex)
V(empty)
建议你把这张图手写一遍,记忆效果最好。
十二、考试最喜欢问的问题
问:
为什么:
需要:
三个信号量?
答案:
| 信号量 | 作用 |
|---|---|
| empty | 保证不会放满 |
| full | 保证不会取空 |
| mutex | 保证互斥 |
问:
为什么:
mutex:
初值:
1?
因为:
一次:
一个线程。
问:
为什么:
full:
初值:
0?
因为:
开始:
没有:
数据。
问:
为什么:
empty:
初值:
n?
因为:
开始:
全部:
都是:
空位。
十三、口诀(★★★★★)
考试前:
一定背。
生产者:
P(empty)
↓
P(mutex)
↓
生产
↓
V(mutex)
↓
V(full)
消费者:
P(full)
↓
P(mutex)
↓
消费
↓
V(mutex)
↓
V(empty)
口诀:
生产先空后锁,消费先满后锁;解锁之后再通知。
这句话能帮助你记住正确的 P/V 操作顺序。
十四、本课重点(★★★★★)
必须掌握:
✅ 三个信号量:
mutex = 1empty = nfull = 0
✅ 生产者:
P(empty);
P(mutex);
生产;
V(mutex);
V(full);
✅ 消费者:
P(full);
P(mutex);
消费;
V(mutex);
V(empty);
✅ 资源信号量(empty/full)要先申请,再申请互斥锁;释放时先释放互斥锁,再通知资源变化。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)