考点频率:★★★★★(PV操作必考经典模型,下午题高频)
难度:⭐⭐⭐⭐
建议:理解 empty 和 full 两个同步信号量的作用,掌握 P 操作的顺序
1️⃣ 问题描述
生产者-消费者问题是操作系统中最经典的同步问题,描述如下:
- 若干个生产者进程往一个共享缓冲区中生产数据(放入产品)
- 若干个消费者进程从同一个共享缓冲区中消费数据(取出产品)
- 缓冲区有大小限制(假设容量为 n)
核心约束:
- 缓冲区满时,生产者必须等待(不能继续放)
- 缓冲区空时,消费者必须等待(不能继续取)
- 同一时刻只能有一个进程访问缓冲区(互斥)
这个问题的本质是同步 + 互斥的组合:生产者和消费者之间是同步关系(满则等、空则等),多个生产者和多个消费者之间是互斥关系(同时只能一人操作缓冲区)。
2️⃣ 信号量设计
解决生产者-消费者问题需要三个信号量:
| 信号量 | 初值 | 作用 |
|---|---|---|
empty | n(缓冲区容量) | 表示空位的数量。生产者每次放入前 P(empty),消费者每次取出后 V(empty) |
full | 0 | 表示已有数据的数量。消费者每次取出前 P(full),生产者每次放入后 V(full) |
mutex | 1 | 互斥信号量,保证同一时刻只有一个进程操作缓冲区 |
其中
empty和full是同步信号量(初值0或n),mutex是互斥信号量(初值1)。
3️⃣ 代码实现
semaphore empty=n;// 空位数量,初值为缓冲区大小 nsemaphore full=0;// 已有数据数量,初值为0semaphore mutex=1;// 互斥信号量// 生产者进程Producer(){while(true){// 生产一个产品 itemP(empty);// 检查是否有空位,没有则阻塞P(mutex);// 进入临界区// 将 item 放入缓冲区V(mutex);// 离开临界区V(full);// 通知消费者:有数据了}}// 消费者进程Consumer(){while(true){P(full);// 检查是否有数据,没有则阻塞P(mutex);// 进入临界区// 从缓冲区取出一个数据 itemV(mutex);// 离开临界区V(empty);// 通知生产者:有空位了// 消费数据 item}}4️⃣ P操作顺序为什么不能颠倒?
在上面的代码中,生产者先执行P(empty)再执行P(mutex),消费者先执行P(full)再执行P(mutex)。
如果把顺序颠倒——先 P(mutex) 再 P(empty) 会怎样?
假设缓冲区已满(empty = 0):
- 生产者执行
P(mutex)→ 成功进入临界区(mutex从1→0) - 生产者执行
P(empty)→ empty=0,生产者被阻塞 - 此时没有消费者能进入缓冲区(因为 mutex=0,所有消费者被挡在
P(mutex)外面) - 生产者等着消费者来消费,消费者却进不来
这就是死锁。
规则:在PV操作中,P操作的顺序是:先申请“资源信号量”(同步),再申请“互斥信号量”。V操作的顺序可以反过来。
5️⃣ 信号量值的动态变化
以缓冲区容量 n=3 为例,观察信号量变化:
初始状态:empty=3, full=0, mutex=1(缓冲区全空)
| 操作 | empty | full | 含义 |
|---|---|---|---|
| 初始 | 3 | 0 | 全空 |
| 生产者放入1个 | 2 | 1 | 还有2个空位 |
| 生产者再放1个 | 1 | 2 | 还有1个空位 |
| 生产者再放1个 | 0 | 3 | 已满 |
| 消费者取走1个 | 1 | 2 | 又有1个空位 |
当
empty=0时,生产者再执行P(empty)会被阻塞,直到消费者取走数据后执行V(empty)唤醒。
6️⃣ 多个生产者和多个消费者的情况
上面的代码可以支持多个生产者进程和多个消费者进程同时运行(生产者与生产者之间也需要互斥吗?需要,因为多个生产者可能同时往缓冲区放数据,会破坏数据结构)。
mutex保证了:
- 生产者之间互斥
- 消费者之间互斥
- 生产者和消费者之间互斥
也就是说,同一时刻只有一个进程(无论是生产者还是消费者)在操作缓冲区。
7️⃣ 经典例题
例题1:某系统有一个容量为10的缓冲区,用PV操作实现生产者-消费者同步。信号量empty初值为10,full初值为0。若当前empty=3,full=7,则表示( )。
A. 缓冲区中有3个数据,7个空位
B. 缓冲区中有7个数据,3个空位
C. 缓冲区已满
D. 缓冲区为空
解析:empty表示空位数=3,full表示已有数据数=7。缓冲区容量=10,3+7=10,正常状态。选B。
例题2:在生产者-消费者问题中,若将生产者代码中的P(empty)和P(mutex)交换顺序,则可能导致( )。
A. 缓冲区数据丢失
B. 死锁
C. 消费者饥饿
D. 数据不一致
解析:先 P(mutex) 再 P(empty) 时,若缓冲区已满,生产者会持有 mutex 锁并阻塞在 empty 上,导致消费者无法进入临界区取数据,造成死锁。选B。
8️⃣ 与单缓冲区的区别
| 对比项 | 单缓冲区(容量1) | 多缓冲区(容量n) |
|---|---|---|
| empty初值 | 1 | n |
| 生产者等待条件 | 缓冲区有数据时等待 | 缓冲区满时等待 |
| 消费者等待条件 | 缓冲区空时等待 | 缓冲区空时等待 |
| 信号量数量 | 2个(full, mutex)或2个(empty, full) | 3个(empty, full, mutex) |
9️⃣ 记忆口诀
生产消费经典题,三个信号量搞定。
empty空位full数,mutex互斥保唯一。
先同步再互斥,P操作顺序别颠倒。
生产P空V满,消费P满V空。
🔟 小测验(评论区对答案)
某系统采用PV操作实现生产者-消费者同步,缓冲区容量为8。当前信号量值为:
empty=2, full=6, mutex=1。此时若生产者执行一次P(empty)操作,则empty的值变为( )。若接着消费者执行一次V(empty)操作,则empty的值变为( )。
A. 1, 2
B. 1, 1
C. 2, 3
D. 0, 1
🔔本专栏日更2篇,点击头像 → 专栏《软考中级高频考点》订阅,第一时间接收新内容
#软考中级 #软件设计师 #生产者消费者 #PV操作 #进程同步 #操作系统