Appearance
生产者-消费者问题
考情分析
生产者-消费者问题是 408 真题中出现频率最高的同步问题,大题几乎必考 PV 操作的变形。属于 🔥🔥🔥 绝对核心。
快递员把包裹放进快递柜,取件人从柜子里取走。柜子满了快递员得等,柜子空了取件人得等,而且两人不能同时对同一个格子操作——这就是生产者-消费者问题的日常版本。
问题描述
- 一组生产者进程向缓冲区中放入数据
- 一组消费者进程从缓冲区中取出数据
- 缓冲区大小为 N
需要满足的同步/互斥关系:
| 关系 | 说明 |
|---|---|
| 互斥 | 生产者和消费者不能同时访问缓冲区 |
| 同步 | 缓冲区满时生产者等待;缓冲区空时消费者等待 |
信号量设计
semaphore mutex = 1; // 互斥信号量:保护缓冲区的互斥访问
semaphore empty = N; // 同步信号量:空闲缓冲区数量
semaphore full = 0; // 同步信号量:已占用缓冲区数量完整解答
// 生产者进程
producer() {
while (true) {
item = produce(); // 生产一个产品
P(empty); // ① 申请一个空缓冲区
P(mutex); // ② 进入临界区
put(item); // 放入缓冲区
V(mutex); // ③ 离开临界区
V(full); // ④ 增加一个满缓冲区
}
}
// 消费者进程
consumer() {
while (true) {
P(full); // ① 申请一个满缓冲区
P(mutex); // ② 进入临界区
item = get(); // 从缓冲区取出
V(mutex); // ③ 离开临界区
V(empty); // ④ 增加一个空缓冲区
consume(item); // 消费产品
}
}P 操作顺序至关重要
P 操作顺序不能颠倒
如果将生产者的 P 操作顺序改为先 P(mutex) 再 P(empty):
// 错误写法!
producer() {
P(mutex); // 先获取锁
P(empty); // 再申请空缓冲区 → 如果缓冲区满,阻塞在这里
... // 但此时 mutex 没有释放!
}缓冲区满时,生产者持有 mutex 但阻塞在 P(empty),消费者想消费但获取不到 mutex → 死锁!
规则:实现互斥的 P 操作一定要在实现同步的 P 操作之后。V 操作的顺序无所谓。
交互可视化
变形题
多生产者多消费者问题
多种类型的生产者和消费者共享缓冲区:
示例:桌上有一个盘子(缓冲区大小=1),父亲放苹果,母亲放橘子,女儿吃苹果,儿子吃橘子。
semaphore plate = 1; // 盘子是否为空
semaphore apple = 0; // 盘中苹果数
semaphore orange = 0; // 盘中橘子数
father() { daughter() {
P(plate); P(apple);
放苹果; 取苹果;
V(apple); V(plate);
} }
mother() { son() {
P(plate); P(orange);
放橘子; 取橘子;
V(orange); V(plate);
} }注意:这里缓冲区大小=1,不需要额外的 mutex(因为 plate 本身就起到了互斥作用)。
吸烟者问题
类似的多资源协作问题,也是经典考题。
考研高频考点
- 🔥🔥🔥 标准的生产者-消费者 PV 操作写法
- 🔥🔥🔥 P 操作顺序不能颠倒(先同步后互斥)
- 🔥🔥🔥 信号量初值的确定(empty=N, full=0, mutex=1)
- 🔥🔥 V 操作顺序可以任意
- 🔥🔥 各种变形问题的信号量设计
生产者-消费者处理的是"一方生产、一方消费"的场景。如果多方同时读、偶尔有人写呢?下一篇看读者-写者问题。