一:生产者-消费者问题(Producer-Consumer Problem)
问题描述
- 场景:多个生产者进程向缓冲区投放数据,消费者进程从中取出数据,缓冲区大小固定(如n个槽位)。
- 核心矛盾:
- 互斥:同一时刻只能有一个进程访问缓冲区(临界资源)。
- 同步:生产者需等待缓冲区未满,消费者需等待缓冲区非空。
关键解决方案(信号量机制)
1. 信号量定义:
- mutex :互斥信号量,初值为1,保证对缓冲区的互斥访问。
- empty :同步信号量,初值为 n (缓冲区空槽数),控制生产者能否放入数据。
- full :同步信号量,初值为0(缓冲区数据项数),控制消费者能否取出数据。
2. 生产者代码逻辑:
while (1) {
生产数据item;
wait(empty); // 等待空槽位
wait(mutex); // 互斥访问缓冲区
将item放入缓冲区;
signal(mutex); // 释放互斥锁
signal(full); // 通知消费者有数据可用
}
3. 消费者代码逻辑:
while (1) {
wait(full); // 等待数据可用
wait(mutex); // 互斥访问缓冲区
从缓冲区取出数据item;
signal(mutex); // 释放互斥锁
signal(empty); // 通知生产者有空槽位
}
考点延伸
- 变种:单缓冲区(无需 mutex ,仅需 empty 和 full )、多生产者-多消费者。
生产者-消费者单缓冲区问题
问题描述:
- 缓冲区大小为1(仅能存放1个数据),生产者每次生产一个数据放入缓冲区,消费者每次从缓冲区取出一个数据。
- 核心矛盾:
- 缓冲区为空时,消费者需等待;缓冲区为满时,生产者需等待。
- 单缓冲区无需考虑互斥(同一时刻只能有一个进程访问),仅需解决同步问题。
- 易错题点:信号量顺序(先同步后互斥,避免死锁)。
二、读者-写者问题(Readers-Writers Problem)
问题描述
- 场景:共享数据可被多个读者同时读取,但写者必须独占访问(读写互斥、写写互斥)。
- 核心矛盾:
- 第一类读者-写者问题:优先保证读者,可能导致写者饥饿。
- 第二类读者-写者问题:优先保证写者,减少写者等待时间。
关键解决方案(信号量机制)
第一类读者-写者(读者优先)
1. 信号量定义:
- read_count :记录当前读者数,初值为0,需用 mutex 互斥保护。
- mutex :保护 read_count 的互斥信号量,初值为1。
- write_lock :读写互斥信号量,初值为1(写者获取锁,读者仅在无写者时读取)。
2. 读者代码逻辑:
wait(mutex); // 互斥访问read_count
if (read_count == 0)
wait(write_lock); // 第一个读者需阻塞写者
read_count++;
signal(mutex); // 释放互斥锁
读取数据;
wait(mutex); // 互斥访问read_count
read_count--;
if (read_count == 0)
signal(write_lock); // 最后一个读者释放写锁
signal(mutex); // 释放互斥锁
3. 写者代码逻辑:
wait(write_lock); // 阻塞所有读者和写者
写入数据;
signal(write_lock); // 释放写锁
第二类读者-写者(写者优先)
- 核心修改:增加一个信号量 wrt 表示写者是否等待,读者需检查是否有写者等待才能进入。
考点延伸
- 核心区别:两类问题的公平性差异,需掌握信号量增减逻辑。
- 适用场景:读多写少场景用读者优先,写多读少用写者优先。
三、哲学家就餐问题(Dining Philosophers Problem)
问题描述
- 场景:n个哲学家围坐餐桌,每人需交替“思考”和“就餐”,就餐时需同时获得左右两根筷子(资源),筷子总数等于哲学家数(每根筷子被两人共享)。
- 核心矛盾:若所有哲学家同时拿左侧筷子,会导致死锁(循环等待资源)。
关键解决方案
1. 避免死锁的策略:
- 策略1:限制同时就餐人数
- 用信号量 sm 限制最多 n-1 个哲学家同时拿筷子,确保至少1人不抢筷,打破循环等待。
- 策略2:奇数拿左筷,偶数拿右筷
- 强制部分哲学家先拿右筷,避免统一方向的资源竞争。
- 策略3:仅当两根筷子都可用时才拿取
- 用信号量数组 chopstick[5] 表示筷子,哲学家拿筷前先检查左右是否可用(需互斥访问状态数组)。
2. 基于信号量的代码示例(策略1):
semaphore chopstick[5] = {1,1,1,1,1}; // 筷子信号量
semaphore sm = n-1; // 限制就餐人数
void philosopher(int i) {
while (1) {
think();
wait(sm); // 等待可用就餐名额
wait(chopstick[i]); // 拿左筷
wait(chopstick[(i+1)%5]); // 拿右筷
eat();
signal(chopstick[i]); // 放左筷
signal(chopstick[(i+1)%5]); // 放右筷
signal(sm); // 释放就餐名额
}
}
考点延伸
- 死锁必要条件:本题是“循环等待”的典型案例,解决方案需针对死锁条件(如破坏循环等待或请求与保持)。
- 易错题点:未考虑筷子的互斥性(每根筷子只能被一人使用),需用信号量正确表示资源。
考研复习要点总结
1. 核心能力:
- 熟练用信号量( wait/signal )实现互斥与同步,区分互斥信号量(初值1)和同步信号量(初值非1)。
- 分析问题中的临界资源、同步关系(如“前操作未完成,后操作需等待”)。
2. 答题模板:
- 明确信号量含义及初值 → 编写进程代码 → 验证是否解决互斥/同步问题 → 分析是否存在死锁/饥饿。
3. 真题方向:
- 变种问题(如带缓冲区的读者-写者、多桌哲学家),需灵活调整信号量逻辑。