操作系统——信号量机制的其他几个问题
之前学了信号量机制的几个问题:
生产者消费者问题: https://www.cnblogs.com/wkfvawl/p/11529681.html
多生产者消费者问题:https://www.cnblogs.com/wkfvawl/p/11531382.html
吸烟者问题:https://www.cnblogs.com/wkfvawl/p/11534452.html
读者写者问题: https://www.cnblogs.com/wkfvawl/p/11538431.html
这里再介绍几个问题。
一、猴子过铁索问题
1、问题描述
两个山崖间有一根铁索,山崖两边各有一群猴子,任何时候同时只能有一个方向的猴子通过铁索。使用P、V操作写出山崖两边的猴子过铁索的算法。
2、问题分析
一个山上的猴子就是一群读者,第二个山上的猴子为另一群读者,两群读者互斥使用铁索。
设信号量waymutex表示山两边的猴子对铁索的互斥共享,初值为1;设m1count和m2count表示对两边猴子的记数,其初值为0;设m1mutex 和m2mutex表示两群猴子中各猴子互斥访问记数变量的信号量,初值都为1,其同步与互斥的算法如下:
semaphore waymutex=1; semaphore m1mutex=1, m2mutex=1; int m1count=0, m2count=0; Monkeygroup1() { while(1) { P(m1mutex); //第一群猴子之间互斥的访问m1count if(m1count==0)//同一方向的该群猴子中的第一个负责“加锁” { P(waymutex); } m1count=m1count+1;// V(m1mutex); 猴子通过铁索; P(m1mutex); m1count=m1count-1; if(m1count==0)//同一方向的该群猴子中的最后一个个负责“解锁” { V(waymutex); } V(m1mutex); } } Monkeygroup2() { while(1) { P(m2mutex);//第二群猴子之间互斥的访问m1count if(m2count=0) { P(waymutex); } m2count=m2count+1; V(m2mutex); 猴子通过铁索; P(m2mutex); m2count=m2count-1; if(m2count=0) { V(waymutex); } V(m2mutex); } }
3、总结
该问题是读者写者问题的变种,读者写者问题中只要求多个读者可以同时对文件读取,写者不可以。这里的铁索和文件是一样的,只有这一份临界资源,但两边的两群猴子同时只能一个方向的猴子通过,也就相当于读者写者问题中,多个写者也可以同时对文件进行操作。
该问题的另外一种描述更加贴近生活:
设A、B两点之间是一段东西向的单行车道,现在要设计一个AB路段自动管理系统,管理规则如下:当AB间有车辆在行驶时同方向的车可以同时驶入AB段,但另一方向的车必须在AB段外等待;当AB段之间无车辆行驶时,到达AB段的任一方向的车都可进入AB段,但不能从两个方向同时驶入,即只能有一个方向的车驶入;当某方向在AB段行驶的车辆驶出了AB段且暂无车辆进入AB段时,应让另一方向等待的车辆进入AB段行驶。试用信号量和P、V操作管理AB路段车辆的行驶。
二、两人下棋问题
1、问题描述
两人下象棋的过程可以概括为:一开始只能是“红先黑后”,以后两人要循环轮流走子,直至某一方获胜或双方和棋为止。这是个只有一个生产者和一个消费者的生产者——消费者问题,是个典型的“你等我,我也等你”的问题。
2、问题分析
红方是总的前趋任务——生产者进程,黑方是总的后继任务——消费者进程,但由于下棋过程必须轮流走子,所以红黑双方的生产者消费者身份会轮流改变。棋盘则是生产者与消费者共享的缓冲。
所用信号量设置如下:
Ⅰ)同步信号量hei,初值为1,表示黑方已走子,开始时可使红方先行不受阻。
Ⅱ)同步信号量hong,初值为0,表示红方尚未走子, 开始时可使黑方先行受阻。
semaphore hei = 1; semaphore hong =0; player1()//红方 { while(1) { P(hei); 若被黑方将死,则投子认负,结束; 若同意与黑方作和,则结束; 否则,根据棋局思考后走一子; V(hong); } } player2()//黑方 { while(1) { P(hong); 若被红方将死,则投子认负,结束; 若同意与红方作和,则结束; 否则,根据棋局思考后走一子; V(hei); } }
3、总结
该问题是消费者生产者问题的变种,不同于生产者消费者问题,该问题中只能有一个红方进程和一个黑方进程,也只有一种同步关系(生产者消费者问题中还有对缓冲池这种临界资源的互斥访问)。同时我们需要注意在下棋的时候总是红方先行,
在“前操作”之后执行V(S)
在“后操作”之前执行P(S)
三、医生与化验室问题
1、问题描述
医生的看病活动:要病人去化验->等待化验结果->继续看病
化验室活动:等待化验单,进行化验,开出化验结果
2、问题分析
(1)同步关系:化验室必须等到有化验单才能开始化验。医生必须等到有化验结果才能继续看病。
(2)设置信号灯Sa:表示是否有化验单,初值为0
设置信号灯Sb:表示是否有化验结果,初值为0。
semaphore sa=0; //是否有化验单
semaphore sb=0; //是否有化验结果
Diagnosis()//医生进程
{
while (看病工作未完成)
{
看病;
V(Sa); //开化验单
P(Sb); //等化验结果
看病;
}
}
Labora()//化验室进程
{
while (化验工作未完成)
{
P(Sa); //等化验单
化验;
V(Sb); //开出化验结果
}
}
3、总结
该问题与前面的红黑双方下象棋的例子类似但不同。我们去医生那里看病,医生先让我们去化验室化验结果,得到化验结果后才能看病,也就是一个医生->化验室->医生的同步关系。上面的红黑双方下棋是红方进程下完棋后黑方进程下棋,黑方进程下完棋后红方进程下棋,但这个问题是在医生进程还没有看完病结束的情况下,让化验室进程化验开出化验结果后,医生进程才可以根据化验单诊断看病,结束进程。
四、超市购物问题
1、问题描述
某小型超级市场,可容纳50人同时购物。入口处有篮子,每个购物者可拿一只篮子入内购物。出口处结帐,并归还篮子(出、入口禁止多人同时通过)。试用信号量和P、V操作写出购物者的同步算法。
2、问题分析
1)互斥关系:a最多只能有50个人同时购物,b顾客对篮子的使用是互斥的。
2)所用信号量设置如下:
Ⅰ)互斥信号量S,初值为50,用以保证最多可以有50个购物者同时进入超市。
Ⅱ)互斥信号量mutex,初值为1,用以保证同时只能有一个购物者进程进入出入口拿起篮子或者结帐后放下篮子。
3)用信号量机制给出的每个购物者购物过程的算法描述如下:
semaphore s = 50; semaphore mutex =1; customer() { while(1) { P(S); P(mutex); 从入口处进超市,并取一只篮子; V(mutex); 进超市内选购商品; P(mutex); 到出口结帐,并归还篮子; V(mutex); 从出口离开超市; V(S); } }
3、拓展
有一个阅览室,共有100个座位,读者进入时必须先在一张登记表上登记,该表为每一座位列一表目,包括座号和读者姓名等,读者离开时要消掉登记的信息,试问:
(1) 为描述读者的动作,应编写几个程序,设置几个进程?
(2) 试用PV操作描述读者进程之间的同步关系。
读者的动作都是一样的:登记进入阅览室,阅读,撤消登记离开阅览室,因此可写一个程序,设n(n≥100)个进程。
读者共享的资源有阅览室的座位和登记表,因此诸个读者进程之间有两种互斥制约关系,需设2个信号量来实现:
seat:用于实现诸读者对阅览室的空闲座位的互斥竞争,初值为100;
mutex:用于实现诸读者对登记表的互斥访问,初值为1。
下面给出一种解法,当然还有其他解法(比如借鉴经典的读者写者问题的解法,利用读者计数器变量)。
semaphore seat = 100; semaphore mutex =1; Reader() { P(seat); /*申请空座位*/ P(mutex); /*申请登记*/ 登记; V(mutex); /*允许其他读者登记*/ 阅读; P(mutex); /*申请撤消登记*/ 撤消登记; V(mutex); /*允许其他读者撤消登记*/ V(seat); /*释放座位,允许他人进入*/ }
五、理发师睡觉问题
1、问题描述
理发店里有一位理发师,一把理发椅和N把供等候理发的顾客坐的椅子。
如果没有顾客,则理发师便在理发椅上睡觉。当一个顾客到来时,他必须先唤醒理发师。
如果顾客到来时理发师正在理发,则如果有空椅子,可坐下来等;否则离开。
2、问题分析
1)同步关系:只要有顾客存在,理发师就不能去睡觉,要去工作。
2)互斥关系:最多有N个顾客,理发师同一时刻只能给一位顾客理发。
2)信号灯设置:Customer:是否有顾客 mutex互斥信号灯 共享变量empty描述空椅子数
semaphore Customer = 0;//同步信号量.是否有顾客 semaphore mutex =1; //实现对empty变量的互斥访问 int empty = N; barber() { while(1) { P(Customer); P(mutex); empty++; V(mutex); 理发; } } customer() { while(1) { P(mutex); if(empty>0)//只有还有空椅子的时候才会解锁 { empty--; V(Customer); 等待理发; } V(mutex); } }
3、总结
该问题可以看成消费者生产者问题的一个变种。可以将理发店的顾客看成生产者,生产需要理发的人数这种资源,理发师看做消费者,给顾客理发,消费这种资源。
与生产者消费者问题不同的是,这里同时有多个生产者进程,但只有一个消费者进程,并且顾客和理发师是同步关系。
六、浴室问题
1、问题描述
一栋学生公寓里,只有一间浴室,且每次仅能容纳一人。公寓里有男生也有女生。因此制定如下规定:
(1)每次只能有一个人在使用浴室;
(2)女生的优先级要高于男生;
(3)对于同性别的人来说,采用先来先服务的原则。
这道题被我们老师改成了使用电话亭.....不用浴室举例,难道是有伤风化?
2、问题分析
下面给出4个函数,要求用信号量和PV原语实现这4个函数,来模拟上述规定下浴室里的过程。(假设初始时浴室为空)
(1)男生想要使用浴室: boy_wants_to_use_bathroom;
(2)男生离开浴室: boy_leaves_bathroom;
(3)女生想要使用浴室: girl_wants_to_use_bathroom;
(4)男生离开浴室: girl_leaves_bathroom;
按照个人理解,boy_wants_to_use_bathroom和girl_wants_to_use_bathroom是随机出现的
而boy_leaves_bathroom和girl_leaves_bathroom是离开浴室后也就是使用完这种临界资源后自动执行的。
定义信号量和其他变量:
int boy_waiting = 0;//正在等待的男生数 int girl_waiting = 0;//正在等待的女生数 int using = 0; //当前是否有人在使用浴室 Semaphore S_mutex = 1;//互斥信号量 Semaphore S_boys = 0;//男生等待队列 Semaphore S_girls = 0;//女生等待队列
男生想要使用浴室
void boy_wants_to_use_bathroom() { P(S_mutex); // 互斥访问 if ((using==0) && (girl_waiting==0)) // 如果浴室空且没有女生在等待 { using = 1; // 使用浴室洗澡 洗澡; V(S_mutex); // 洗完澡把浴室资源释放 } else { boy_waiting++; // 男生等待队列加长 V(S_mutex); // 释放资源 P(S_boys); // 如果轮不到男生上手,阻塞:排队等着 } }
男生离开浴室
void boy_leaves_bathroom() { P(S_mutex); // 互斥访问 if (girl_waiting>0) // 先考虑女生 { girl_waiting--; // 女生等待队列减少 V(S_girls); // 唤醒女生想要使用浴室进程 } else if (boy_waiting>0) // 再考虑男生 { boy_waiting--; // 男生等待队列减少 V(S_boys); // 唤醒男生想要使用浴室进程 } else { using = 0; // 如果没有人等待了,就把显示浴室没有人用 } V(S_mutex); // 释放资源 }
女生想要使用浴室
void girl_wants_to_use_bathroom() { P(S_mutex); // 互斥访问 if (using==0) // 如果浴室没有人 { using = 1; // 使用浴室洗澡 洗澡; V(S_mutex); // 洗完澡把浴室资源释放 } else { girl_waiting++; // 女生等待队列增加 V(S_mutex); // 释放资源 P(S_girls); // 如果轮不到女生上手,阻塞:排队等着 } }
女生离开浴室
void girl_leaves_bathroom() { boy_leaves_bathroom(); // 和男生离开浴室一样 }
3、总结
这应该是到目前为止最难的一道PV操作题了,我最开始想不明白那个using=0是什么时候设置的,后来想了想信号量的数据结构就明白了,那些在等待的进程都被挂在信号量那个队列上了,也就是说上一个进程执行完,操作系统自动的从队列中取队首的那个进程。而需要设置using=0是队列中没有任何进程了,也就是说using=1是最开始的第一个使用浴室的进程设置的,而using=0是最后一个离开浴室的进程设置的。