本文最后更新于125 天前,其中的信息可能已经过时,如有错误请发送邮件到1910452164@qq.com
这是一篇针对操作系统考研中 PV操作(信号量机制) 经典大题的解题模板总结。在考试中,解答此类题目通常需要遵循 “定义信号量 -> 编写进程伪代码” 的步骤。
以下是四大经典模型的标准解题模板:
生产者-消费者模型
这是最基础也是最重要的模型,核心在于处理缓冲区的互斥访问以及缓冲区满/空的同步问题。
解题关键
- 互斥信号量 (
mutex):保护缓冲区,防止同时存取。 - 同步信号量 (
full/empty):full:表示缓冲区中“产品”的数量(初值为0)。empty:表示缓冲区中“空位”的数量(初值为N)。
标准代码模板
semaphore mutex = 1; // 临界区互斥信号量
semaphore empty = N; // 空缓冲区数量
semaphore full = 0; // 满缓冲区数量(产品数)
// 生产者进程
void Producer() {
while(true) {
produce_item(); // 生产一个产品
P(empty); // 申请一个空位 (若无空位则阻塞)
P(mutex); // 申请进入缓冲区 (互斥)
put_item(); // 将产品放入缓冲区
V(mutex); // 退出缓冲区
V(full); // 释放一个产品信号 (通知消费者)
}
}
// 消费者进程
void Consumer() {
while(true) {
P(full); // 申请一个产品 (若无产品则阻塞)
P(mutex); // 申请进入缓冲区
take_item(); // 从缓冲区取出一个产品
V(mutex); // 退出缓冲区
V(empty); // 释放一个空位信号 (通知生产者)
consume_item(); // 消费产品
}
}
哲学家进餐问题
核心在于死锁(Deadlock)的预防。如果是普通的教科书式解法(每人先拿左再拿右),会导致死锁。考研中通常建议使用“限制策略”或“奇偶分层策略”来作为标准答案。
解题关键
- 资源信号量:5根筷子,每根是一个信号量。
- 死锁解决:
- 方案A:仅当左右两根筷子都可用时才拿(需要在一个临界区内判断)。
- 方案B:限制最多4人同时进餐。
- 方案C(推荐模板):奇数号哲学家先拿左边,偶数号哲学家先拿右边。
标准代码模板 (奇偶策略防死锁版)
semaphore chopstick[5] = {1, 1, 1, 1, 1}; // 5根筷子
void Philosopher(int i) { // i 为哲学家编号 0-4
while(true) {
think(); // 思考
if (i % 2 == 0) {
// 偶数号哲学家:先拿左(i),后拿右((i+1)%5)
P(chopstick[i]);
P(chopstick[(i + 1) % 5]);
} else {
// 奇数号哲学家:先拿右((i+1)%5),后拿左(i)
// 这样打破了环路等待条件
P(chopstick[(i + 1) % 5]);
P(chopstick[i]);
}
eat(); // 进餐
// 放下筷子(顺序无所谓,通常怎么拿怎么放)
V(chopstick[i]);
V(chopstick[(i + 1) % 5]);
}
}
读者-写者模型
核心在于处理“读-读允许,读-写互斥,写-写互斥”。最常考的是“读者优先”(只要有读者在读,后来的读者可以直接进,写者必须等所有读者走完)。
解题关键
- 读写互斥 (
rw):控制写者与读者的互斥。 - 计数器 (
count):记录当前读者的数量。 - 计数器互斥 (
mutex):保护count变量的修改。
标准代码模板 (读者优先)
int count = 0; // 记录当前读者数量
semaphore mutex = 1; // 保护 count 变量
semaphore rw = 1; // 保证读者和写者互斥访问文件
// 写者进程
void Writer() {
while(true) {
P(rw); // 申请写权限 (若有读者或写者在操作则阻塞)
writing(); // 写操作
V(rw); // 释放写权限
}
}
// 读者进程
void Reader() {
while(true) {
P(mutex); // 申请修改 count
if (count == 0) {
P(rw); // 如果是第一个读者,负责锁住写者
}
count++;
V(mutex); // 释放 count 锁
reading(); // 读操作 (多个读者可同时在此处)
P(mutex); // 申请修改 count
count--;
if (count == 0) {
V(rw); // 如果是最后一个读者,负责释放写者
}
V(mutex); // 释放 count 锁
}
}
理发师模型
这是一个典型的服务排队模型。核心在于理发师在无顾客时睡眠,顾客来时唤醒理发师;若理发师忙,顾客判断是否有空位等待。
解题关键
- 变量
waiting:记录等待理发的顾客数。 - 信号量
customers:表示等待的顾客数量(用来唤醒理发师)。 - 信号量
barbers:表示理发师的状态(0表示忙或睡,1表示准备好/空闲,或者用来阻塞顾客)。 - 互斥
mutex:保护waiting变量。
标准代码模板
semaphore customers = 0; // 等待理发的顾客数 (同步)
semaphore barbers = 0; // 理发师空闲信号 (同步)
semaphore mutex = 1; // 互斥访问 waiting 变量
int waiting = 0; // 等待椅上的人数
int CHAIRS = N; // 椅子总数
// 理发师进程
void Barber() {
while(true) {
P(customers); // 等待顾客 (若无顾客则睡觉/阻塞)
P(mutex); // 申请访问 waiting
waiting--; // 等待人数 -1
V(barbers); // 发信号:理发师准备好了/开始理发
V(mutex); // 释放 waiting 访问权
cut_hair(); // 理发 (非临界区)
}
}
// 顾客进程
void Customer() {
P(mutex); // 申请访问 waiting
if (waiting < CHAIRS) {
waiting++; // 还有空位,坐下等待
V(customers); // 唤醒理发师 (或是增加一个顾客信号)
V(mutex); // 释放 waiting 访问权
P(barbers); // 等待理发师叫号 (阻塞直到理发师空闲)
get_haircut(); // 接受理发
} else {
V(mutex); // 没空位了,直接离开
leave();
}
}
注意,这里的customer进程中没有while(true)的循环,只有一个if-else的分支判断。
不会
我真的不会
不会不会不会