1. 生产者-消费者问题
生产者消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(英语:Bounded-buffer problem),是一个多线程同步问题的经典案例。该问题描述了两个共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。
要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆就放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者。通常采用进程间通信的方法解决该问题,常用的方法有信号灯法等。如果解决方法不够完善,则容易出现死锁的情况。出现死锁时,两个线程都会陷入休眠,等待对方唤醒自己。该问题也能被推广到多个生产者和消费者的情形。
利用 记录型信号量 解决生产者-消费者问题
// 初始化信号量
semaphore full = 0; // 满缓冲区数目
semaphore empty = n; // 空缓冲区数目
semaphore mutex = 1; // 对缓冲区操作的互斥信号量
// 生产者
producer() {
while(1) {
produce an item in nextp; // nextp 为临时缓冲区
P(empty); // 申请一个空缓冲区
P(mutex); // 申请使用缓冲区,进入区
put the item into buffer; // 临界区
V(mutex); // 缓冲区使用完毕,释放互斥信号量
V(full); // 增加一个满缓冲区
}
}
// 消费者
consumer() {
while(1) {
P(full); // 申请一个满缓冲区
P(mutex); // 申请使用缓冲区
取走产品; // 临界区
V(mutex); // 缓冲区使用完毕,释放互斥信号量
V(empty); // 增加一个空缓冲区
consume the item; // 消费掉产品
}
}
2. 哲学家进餐问题
有五个哲学家,他们的生活方式是交替地进行思考和进餐,哲学家们共用一张圆桌,分别坐在周围的五张椅子上,在圆桌上有五个碗和五支筷子,平时哲学家进行思考,饥饿时便试图取其左、右最靠近他的筷子,只有在他拿到两支筷子时才能进餐,该哲学家进餐完毕后,放下左右两只筷子又继续思考。
约束条件
(1) 只有拿到两只筷子时,哲学家才能吃饭。
(2) 如果筷子已被别人拿走,则必须等别人吃完之后才能拿到筷子。
(3) 任一哲学家在自己未拿到两只筷子吃完饭前,不会放下手中已经拿到的筷子。
利用 AND 信号量机制 解决哲学家进餐问题
// 五个哲学家进餐问题
semaphore chopstick[5] = {1, 1, 1, 1, 1}; // 5 根筷子信号量初值都是 1
philosopher() {
while(1) {
think; // 思考
// 想吃饭
Swait(chopstick[i], chopstick[(i + 1) % 5]); // 要求每个哲学家先获得两个临界资源(筷子)后方能进餐
eat; // 吃
Signal(chopstick[i], chopstick[(i + 1) % 5]); // 放下筷子,释放资源
}
}
3. 吸烟者问题
假设一个系统有三个抽烟者进程和一个供应者进程。每个抽烟者不停地卷烟并抽掉,但是要卷起并抽掉一支烟,需要三种材料:烟草、纸、胶水。三个抽烟者中,每一个第一个拥有烟草,第二个拥有纸,第三个拥有胶水。供应者无限提供三种材料,供应者每次将两种材料放桌子上,拥有剩下材料的抽烟者卷一支烟并抽掉它,并给供应者一个信号完成了,供应者就会把另外两种材料再放桌子上,这个过程一直重复(三个抽烟者轮流抽烟)
组合一:纸 + 胶水
组合二:烟草 + 胶水
组合三:纸 + 烟草