奇偶数生产者-消费者问题
三个进程 P1、P2、P3 互斥使用一个包含 N(N>0)个单元的缓冲区。
P1 每次用 produce()生成一个正整数并用 put()送入缓冲区某一个空单元中;
P2 每次用 getodd()从该缓冲区中取出一个奇数并用 countodd()统计奇数个数;
P3 每次用 geteven()从该缓冲区中取出一个偶数并用 counteven()统计偶数个数。
请用信号量机制实现这三个进程的同步与互斥活动, 并说明所定义的信号量的含义。
答:想象P1为生产者,P2、P3为消费者。
P1、P2共享缓冲区的奇数部分,故设置同步信号量odd;
P1、P3共享缓冲区的偶数部分,故设置同步信号量even;
P1、P2、P3共享整个缓冲区,用一个互斥mutex来保护,再设置同步信号量empty,表示缓冲区空位资源。
伪代码如下:
#define N 100
Semaphore empty = N; // 假设初始条件下缓冲区有N个空位
Semaphore mutex = 1;
Semaphore odd = 0;
Semaphore even = 0;
void P1(){
int integer;
while(true){
integer = produce(); // 生成一个整数
P(empty); // down(empty),若empty为0则会被阻塞(等待别人拿走)
P(mutex); // 开始互斥,down(mutex)
put(); // 放入缓冲区
V(mutex); // 访问临界区结束,up(mutex)
if(integer %2 == 0){
V(even); // 是偶数
} else {
V(odd); // 是奇数
}
}
}
void P2(){
while(true){
P(odd); // 请求一个奇数,down(odd)
P(mutex); // 互斥
getodd();
V(mutex);
V(empty); // 缓冲区多一个位置,up(empty)
countodd();
}
}
void P3(){
while(true){
P(even); // 请求一个偶数,down(even)
P(mutex); // 互斥
geteven();
V(mutex);
V(empty); // 缓冲区多一个位置,up(empty)
counteven();
}
}
野人吃肉
一个野人部落从一个大锅中一起吃炖肉,这个大锅一次可以存放 M 人份的炖肉。
当野人们想吃的时候,如果锅中不空,他们就自助着从大锅中吃肉。如果大锅空了,他们就叫 醒厨师,等待厨师再做一锅肉。
答:野人和厨师共享缓冲区锅,设置互斥信号量mutex;
厨师一次性往锅里放M份肉,设置厨师信号量cook,整形变量meat记录剩下的肉数量。
野人没吃完时,厨师在睡觉,设置野人信号量wild;
当大锅空的时候,野人不能够调用 getServingFromPot() ;
仅当大锅为空的时候,大厨才能够调用 putServingsInPot()
#define M 100
Semaphore mutex = 1;
Semaphore cook = 0, wild = 0;
int meat = 0;
void wildman(){
while (true){
P(mutex);
if (meat > 0){ // 锅里还有肉
getServingFromPot();
eat();
meat = meat - 1;
V(mutex); // 吃完一份,释放缓冲区占用
}
else{
V(wild); // 唤醒睡觉的厨师
P(cook); // 请求一个厨师加肉,若厨师为0则被阻塞(等待煮肉)
V(mutex);
}
}
}
void Cook(){
while (true) {
P(mutex);
if (meat == 0){ // 煮肉ing
putServingsInPot(M);
meat = meat + M;
V(mutex);
V(cook); // 厨师做好了,可以唤醒野人等待进程继续吃
}
else{
// 锅里不空,不用煮肉,睡觉(野人为0,没有人叫他,阻塞)
P(wild);
}
}
}
缓冲区产品
系统中有多个生产者进程和消费者进程,共享用一个可以存 1000 个产品的缓冲区(初始为空),当缓冲区为未满时,生产者进程可以放入一件其生产的产品,否则等待;
当缓冲区为未空时,消费者进程可以取走一件产品,否则等待。
要求一个消费者进程从缓冲区连续取出 10 件产品后,其他消费者进程才可以取产品,请用信号量 P,V 操作实现进程间的互斥和同步,要求写出完整的过程;并指出所用信号量的含义和初值。
答:生产者可以一直不停地放,除非满了则阻塞,设置同步信号量empty表示空的数量,同步信号量stuff表示产品的数量;
生产者和消费者共享一个缓冲区,设置互斥信号量mutex;
有所不同的是,一个消费者必须一次拿1件产品,连续拿10次,读10次缓冲区,该过程不可中断。
#define N 1000;
Semaphore mutex = 1;
Semaphore mutex_get = 1;
Semaphore empty = N;
Semaphore stuff = 0;
void producer(){
while(true){
P(empty); // 若empty为0没有空闲则等待
P(mutex);
puts(); // 放入一件产品
V(mutex);
V(stuff); // 释放产品,可以唤醒消费者等待拿走
}
}
void consumer(){
int i = 0;
while(true){
// 当一个消费者P(mutex)时其他消费者P(mutex)会被阻塞(等待别人拿完)
P(mutex); // 占用缓冲区直到拿走10个再释放
for(i = 0; i < 10; i++){
P(stuff); // down(stuff),产品不足会被阻塞
P(mutex_get);
gets(); // 取走一件物品
V(mutex_get);
V(empty); // up(empty)
}
V(mutex); // 释放互斥
}
}
寿司店问题
假设一个寿司店有 5 个座位,如果你到达的时候有⼀个空座位,你可以立刻就坐。但是如 果你到达的时候 5 个座位都是满的有⼈已经就坐,这就意味着这些人都是一起来吃饭的,那么你需要等待所有的⼈一起离开才能就坐。编写同步原语,实现这个场景的约束。
- 使用整型变量
eating
和waiting
记录在寿司店就餐和等待的线程,使用信号量mutex
对这两个变量的读写进行保护 - 使用信号量
queue
表示排队 - 使用布尔变量
must_wait
表示寿司店现在满座,新来的顾客必须等待
int eating = 0, waiting = 0;
Semaphore mutex = 1, queue = 1;
bool must_wait = false;
Customer(){
P(mutex);
if (must_wait){
waiting++;
V(mutex); //对waiting变量的保护可以释放
P(queue); // 被阻塞,坐着等待排队,等待被唤醒
}
else {
eating++;
must_wait = (eating == 5)
// 一旦我是最后一个来坐下吃导致人满的就要等所有人一起吃完,好难过
V(mutex); // 对eating变量的保护可以释放
}
// 上一部分已经解决了进店后是等待还是吃的问题
Eat_sushi();// else的人和被唤醒的排队者成功进入这一步
P(mutex); // 开启对eating, waiting变量保护
eating--; // 吃的人-1,如果5个没全吃完,不可以换下一批人吃
if (eating == 0){ // 最后一个吃完的人离开才可以进顾客
int n = min(5, waiting); // 放顾客进来的数量,不超过5个
waiting -= n;
eating +=n;
must_wait = (eating == 5)
for(int i = 0; i<n; i++){
V(queue); // 唤醒排队的n个人继续进程
}
V(mutex); // 允许下一个吃完的人对变量和队列进行操作
}
搜索-插入-删除问题
三个线程对一个单链表进行并发的访问,分别进行搜索、插入和删除。搜索线程仅仅读取链表,因此多个搜索线程可以并发。
插入线程把数据项插入到链表最后的位置;多个插入线程必须互斥防止同时执行插入操作。但是,一个插入线程可以和多个搜索线程并发执行。
最后,删除线程可以从链表中任何一个位置删除数据。 一次只能有一个删除线程执行;删除线程之间,删除线程和搜索线程,删除线程和插入线程都不能同时执行。
请编写三类线程的同步互斥代码,描述这种三路的分类互斥问题。
- 这个问题与读者写者有些类似
Semaphore insertMutex =1, searchMutex = 1; // 保护int变量
Semaphore No_search = 1; // 顾名思义,为1时没有搜索进程访问
Semaphore No_insert = 1; // 为1时没有插入进程访问
//当上述两个信号量同时为1,删除者才可以进行删除操作
int searcher = 0, inserter = 0;
void Search(){
P(searchMutex);
searcher++;
if (searcher == 1) // 第一个进来的搜索者加锁
P(No_search)
V(searchMutex);
Searching(); // 访问临界区,多个搜索无需互斥
P(searchMutex);
searcher--;
if (searcher == 0)
V(No_search); // 表示此时没有搜索线程在进行,解锁
V(searchMutex);
}
void Insert(){
P(insertMutex);
inserter++;
if (inserter == 1)
P(No_insert)
V(insertMutex);
P(insertMutex); // 既然可以和搜索线程并行,那么不用管Searcher
Inserting(); // 访问临界区,多个插入者要互斥访问,一次一个insert
V(insertMutex);
P(insertMutex);
inserter--;
if (inserter == 0)
V(No_insert); // 解锁,可唤醒删除者
V(insertMutex);
}
void Delete(){ // 删除线程与其他任何线程互斥
P(No_search);
P(No_insert); // 若为1则可进入,这个信号量顺便也可以当作删除者的互斥保护
Deleting(); // 搜索和插入线程都没,成功进入临界区
V(No_insert);
V(No_search);
}