✅ 生产消费者
六步骤:
一、明确进程分类
确定有几类进程,每类进程对应一个函数 ,清晰区分不同角色(如生产者、消费者,或案例里的 “同学、大师傅” )。
二、描述进程动作
在进程函数内部,用中文描述核心行为:
- 若动作 “只做一次”,不加
while(1)
循环; - 若动作 “不断重复”,用
while(1)
包裹逻辑。
三、分析 PV 操作
梳理进程动作前是否需P
操作(申请资源 / 锁):
- 只要用
P
(申请),必须配对V
(释放),保证资源 / 信号量正确回收; - 注意 “隐含互斥”,比如操作临界资源(共享变量、设备),需加
P(mutex)
等互斥信号量。
四、定义信号量
所有P
/V
逻辑写完后,再定义信号量(如Semaphore
类型),并确定初始值:
- 需结合资源初始状态(如 “rice=0 、 one=100” ,根据生产 / 消费逻辑设置 )。
五、检查死锁风险
重点看连续P
操作:
- 若多个
P
连续出现,尝试调整顺序,避免循环等待; - 若某信号量
P
/V
“连续且无其他P
插入”,因不破坏 “请求和保持”,一般不会死锁。
六、验证题目要求
最后读题复盘:确认进程逻辑、PV 配对、资源交互是否贴合题目场景(如生产消费流程、资源数量约束等 ),保证模型满足需求。
核心逻辑:通过 “分类→动作→PV→信号量→死锁→验证” 六步,逐步搭建进程同步模型,用 PV 操作解决 “生产 - 消费” 的资源协调问题,同时规避死锁风险 。
✅ 同步问题和生产消费同时完成:
定义五类:
// 进程A:独立执行,完成后释放信号量通知后续进程
A() {
操作A; // 进程A的核心任务(无前置依赖)
V(A完成); // A执行完毕,释放" A完成 "信号量(值+1)
}
// 进程B:独立执行,完成后释放信号量通知后续进程
B() {
操作B; // 进程B的核心任务(无前置依赖)
V(B完成); // B执行完毕,释放" B完成 "信号量(值+1)
}
// 进程C:依赖A和B完成后才能执行
C() {
P(A完成); // 等待A执行完毕(若A未完成则阻塞)
P(B完成); // 等待B执行完毕(若B未完成则阻塞)
操作C; // 只有A和B都完成后,才能执行C的任务
V(C完成); // C执行完毕,释放" C完成 "信号量(值+1)
}
// 进程D:独立执行,完成后释放信号量通知后续进程
D() {
操作D; // 进程D的核心任务(无前置依赖)
V(D完成); // D执行完毕,释放" D完成 "信号量(值+1)
}
// 进程E:依赖C和D完成后才能执行
E() {
P(C完成); // 等待C执行完毕(若C未完成则阻塞)
P(D完成); // 等待D执行完毕(若D未完成则阻塞)
操作E; // 只有C和D都完成后,才能执行E的任务
}
✅ 哲学家进餐(只有一种资源才能运行)
操作模板:使用int变量表示资源
// 定义互斥大锁(用于资源操作的同步)
semaphore Lock = 1;
// 定义各类资源的剩余数量
int a = 9; // 资源a剩余数量
int b = 8; // 资源b剩余数量
int c = 5; // 资源c剩余数量
Process() {
while(1) {
P(Lock); // 申请锁,进入临界区
// 检查所需资源是否充足
if(所有资源都满足需求) {
// 按需求减少对应资源的剩余数量
各类资源int--; // 具体减少数量根据题目要求确定
// 一次性获取所有所需资源
取对应资源;
V(Lock); // 释放锁,退出临界区
break; // 成功获取资源,跳出循环
}
V(Lock); // 资源不足,释放锁,等待下次尝试
}
// 执行进程的核心任务(例如:哲学家进餐)
做进程该做的事;
P(Lock); // 申请锁,进入临界区
// 一次性归还所有资源,恢复资源剩余数量
各类资源int++; // 归还数量与之前获取的数量一致
V(Lock); // 释放锁,退出临界区
}
✅ 顾客-服务员并发模型(含忙等 / 睡觉 / 限制)
本模型模拟服务员与顾客之间的并发关系,通过 信号量机制 实现同步,同时 限制顾客数量,控制系统资源使用,防止过载。
🔧 代码实现(含注释)
👥 顾客线程 customer_i()
customer_i() {
P(mutex); // 取号(进入临界区)
if (waiting >= MAX) { // 如果顾客太多
V(mutex); // 离开前释放mutex
return; // 顾客离开
}
取号;
waiting++; // 顾客数+1
V(mutex); // 离开临界区
等待被叫号;
V(customer); // 通知服务员有顾客到达
P(service); // 等待服务员叫号
被服务;
}
👨🔧 服务员线程 server_i()
server_i() {
while (1) {
P(mutex); // 进入临界区
if (waiting > 0) {
叫号;
waiting--; // 顾客数-1
V(mutex); // 离开临界区
// 提供服务
V(service); // 通知顾客可以被服务了
} else {
V(mutex); // 无顾客,释放锁
P(customer); // 睡觉(阻塞)
}
}
}
✅ 三个关键行为总结
1️⃣ 服务人员是忙等(✅ 是)
while (1) {
P(mutex);
if (waiting > 0) {
...
} else {
V(mutex);
// ❌ 没有阻塞操作
}
}
📌 说明:
-
服务员每次循环都 主动抢占 mutex,检查 waiting 值。
-
即使没有顾客,也会进入临界区检查再决定是否休眠。
-
所以不是完全阻塞等待,而是“忙等 + 条件阻塞”的组合。
✅ 结论:
服务员不是被动等待,而是持续检查顾客状态,属于忙等行为(Busy Waiting)。
2️⃣ 服务人员会睡觉(✅ 是)
等待被叫号;
V(customer); // 通知服务员有顾客到达
P(customer); // 无顾客时阻塞
📌 说明:
-
当
waiting == 0
时,服务员执行P(customer)
,线程会阻塞。 -
直到有顾客调用
V(customer)
才能唤醒服务员。 -
这是一种节省 CPU 的阻塞睡眠机制,模拟现实中“服务员打盹”。
✅ 结论:
服务员在无顾客时会“睡觉”,等待被顾客唤醒,符合并发控制设计。
3️⃣ 顾客数有限(✅ 是)
if (waiting >= MAX) {
V(mutex);
return;
}
📌 说明:
-
通过
if (waiting >= MAX)
判断是否已超出等候限制。 -
如果等候人数已满,顾客释放 mutex 后离开。
-
实现了类似“座位数有限”的约束,控制资源使用。
✅ 结论:
顾客数受限,模拟“座位有限”现实逻辑,防止系统过载。
📌 最终总结表格(最新版)
特性名称 | 是否实现 | 说明 |
---|---|---|
服务人员是忙等 | ✅ 是 | 服务员每轮循环都执行 P(mutex) 并检查 waiting ,不是被动等待顾客唤醒,有忙等行为。 |
服务人员会睡觉 | ✅ 是 | 如果没有顾客,服务员最终会进入 P(customer) 阻塞状态,模拟“打盹”行为。 |
顾客有座位限制 | ✅ 是 | 若 waiting >= MAX ,顾客立即离开,不再加入队列,模拟座位已满的情况。 |
🧠 结语总结一句话:
本并发模型通过信号量和互斥锁机制控制顾客与服务员的同步,具备“有限排队、忙等检查、空闲可睡”三种典型行为,适合理解操作系统中的线程同步问题。