Appearance
题目
三个进程 P1、P2、P3 互斥使用一个包含 N(N > 0)个单元的缓冲区:
- P1:每次用
produce()生成一个正整数,并用put()送入缓冲区某一空单元 - P2:每次用
getodd()从该缓冲区中取出一个奇数,并用countodd()统计奇数个数 - P3:每次用
geteven()从该缓冲区中取出一个偶数,并用counteven()统计偶数个数
请用信号量机制实现这三个进程的同步与互斥活动,并说明所定义信号量的含义。要求用伪代码描述。
解析
关系拆解
先把三个进程之间的关系列清楚:
| 关系类型 | 涉及进程 | 用什么信号量 | 为什么 |
|---|---|---|---|
| 互斥 | 三方共享缓冲区,一次只能一个进程操作 | mutex (init=1) | put/getodd/geteven 都要改缓冲区状态 |
| 同步:缓冲区满 / 空 | P1 不能往满了的缓冲区放、P2/P3 不能从空缓冲区取 | empty (init=N) | 经典 PV 容量管理 |
| 同步:奇数到达通知 P2 | P1 放了奇数 → 通知 P2 | odd (init=0) | P2 只取奇数 |
| 同步:偶数到达通知 P3 | P1 放了偶数 → 通知 P3 | even (init=0) | P3 只取偶数 |
注意一个细节:P2 和 P3 之间没有直接同步关系——它们各取各的,被分开的奇 / 偶信号量隔离了。
信号量与代码
c
semaphore mutex = 1; // 互斥访问缓冲区
semaphore empty = N; // 缓冲区空闲单元数
semaphore odd = 0; // 缓冲区中已放入但未取走的奇数个数
semaphore even = 0; // 缓冲区中已放入但未取走的偶数个数
P1() {
while (true) {
x = produce(); // 生成一个正整数
P(empty); // 等空单元
P(mutex); // 占用缓冲区
put(); // 放入
V(mutex); // 让出缓冲区
if (x % 2 == 0)
V(even); // 唤醒等待偶数的 P3
else
V(odd); // 唤醒等待奇数的 P2
}
}
P2() {
while (true) {
P(odd); // 等"有奇数可取"
P(mutex); // 占用缓冲区
getodd(); // 取出奇数
V(mutex); // 让出缓冲区
V(empty); // 缓冲区多出 1 个空单元
countodd(); // 统计(不需要互斥,本地变量)
}
}
P3() {
while (true) {
P(even); // 等"有偶数可取"
P(mutex); // 占用缓冲区
geteven(); // 取出偶数
V(mutex); // 让出缓冲区
V(empty); // 缓冲区多出 1 个空单元
counteven(); // 统计
}
}信号量含义说明(答题必写)
| 信号量 | 初值 | 含义 |
|---|---|---|
mutex | 1 | 缓冲区互斥锁,保证 put/getodd/geteven 三种操作不并发 |
empty | N | 缓冲区空闲单元数;P1 进入前要先 P(empty),取走后 V(empty) |
odd | 0 | 缓冲区中待取的奇数个数;P1 放奇数后 V,P2 取走前 P |
even | 0 | 缓冲区中待取的偶数个数;P1 放偶数后 V,P3 取走前 P |
易错点逐条复盘
编者注(易错点 / 关键设计):
P 操作的顺序:
P(empty)必须在P(mutex)之前——否则会死锁。设想 P1 已拿到 mutex 但 empty=0,它会卡在 P(empty) 上而 mutex 不释放,P2/P3 也进不来 → 死锁。永远是"等资源的同步信号量在外层、互斥信号量在内层"。唤醒 P2 / P3 必须在 V(mutex) 之后吗? 不必。本题答案把 V(odd)/V(even) 放在 V(mutex) 之后,逻辑上没问题(先释放锁、再发通知)。但也可以放在 V(mutex) 前——因为 V 操作不会阻塞、不会引起死锁,只影响 P2/P3 唤醒的时机。两种写法都给分。
不要给 P2 和 P3 共用一个"data 信号量":错误写法是定义
semaphore data = 0,P1 放完后 V(data)、P2 P3 都 P(data)。这样会导致奇偶错配——P3 可能 P 到一个,但里面是奇数,无法处理(geteven 取不到)。正确做法必须奇偶各一个信号量。统计函数(countodd / counteven)放在 V(mutex) 之外:因为统计是 P2 或 P3 的本地变量操作,与缓冲区无关,没必要在临界区内执行——能减小临界区粒度、提高并发度。这是 408 评分细节里的"加分项"。
奇 + 偶信号量值之和总是等于"缓冲区当前已放但未取的总数":可以作为答题时的不变式自检。如果代码里出现"V(odd) 后又 V(odd)"或类似失衡情况,多半是逻辑写错了。