理发师问题与生产者消费者问题不同,生产者消费者问题是“生产-消费”问题,理发师问题是“服务-被服务”的问题。然而,这两个问题从根本上来说思路是一样的,下面请大家仔细研究这几个模板。
【万能模板 1】店里有 N 名服务人员,没有顾客时服务人员休息,有顾客时就叫号。
顾客到店时,需要先取号,并叫醒一名休息的服务人员(如果有),并等待叫号。
【思路】本题类似于生产者消费者问题,顾客是生产者,生产顾客资源;服务人员是消费者,消费顾客资源。反过来,服务人员是生产者,生产服务人员资源;顾客是消费者,消费服务人员资源。所以可以按生产者消费者的思路来解答。
服务人员叫号后,意味着有一个服务人员前来提供服务,可视为生产了一个服务人员;顾客取号后,意味着多了一名顾客在等待,可视为生产了一名顾客。而取号和叫号类似于放入缓冲区的操作,需要一个互斥锁,这里先省略不写,最后再来写。
用中文描述如下:
Server(){
while(1){
P(店里是否有顾客?若有,顾客-1,否则阻塞); // 服务人员占用一个顾客资源(注意这里的阻塞已经充当服务人员休息的作用了)
叫号;
V(服务人员+1);
提供服务;
}
}
Customer(){
取号;
V(顾客+1); // (注意这里的释放已经充当唤醒服务人员的作用了)
P(店里是否有服务人员?若有,服务人员-1,否则阻塞); // 顾客占用一个服务人员资源
被服务;
}
初始时,顾客进程和服务人员进程均未开始生产顾客和服务人员,因此两者的信号量初始值设为 0,现翻译成英文:
信号量 customer = 0; // 顾客资源数
信号量 server = 0; // 服务人员资源数
Server(){
while(1){
P(customer);
叫号;
V(server);
提供服务;
}
}
Customer(){
取号;
V(customer);
P(server);
被服务;
}
最后加上互斥信号量:
信号量 customer = 0; // 顾客资源数
信号量 server = 0; // 服务人员资源数
信号量 mutex = 1;
Server(){
while(1){
P(customer);
P(mutex);
叫号;
V(mutex);
V(server);
提供服务;
}
}
Customer(){
P(mutex);
取号;
V(mutex);
V(customer);
P(server);
被服务;
}
【万能模板 2】店里有 N 名服务人员,没有顾客时服务人员休息,有顾客时就叫号。
店里有 M 个空位。顾客到店时,需要先检查是否 M 个位置被其他顾客都占用了,若是,则离店;若否,则占用一个位置取号等待,并叫醒一名休息的服务人员(如果有),并等待叫号。
【思路】可以先在上题的基础上增加一个变量 waiting,记录当前正在等待的顾客数。当顾客取号后,顾客等待数量加 1,;当服务人员叫号后,顾客等待数量减 1,如下:
(//
为新加入的代码,下同)
信号量 customer = 0; // 顾客资源数
信号量 server = 0; // 服务人员资源数
int waiting = 0; // 正在等待的顾客数量
Server(){
while(1){
P(customer);
叫号;
waiting--; //
V(server);
提供服务;
}
}
Customer(){
取号;
waiting++; //
V(customer);
P(server);
被服务;
}
下面只需在顾客进程加入一个判断,若顾客等待数量超过上限,则直接离店,如下:
信号量 customer = 0; // 顾客资源数
信号量 server = 0; // 服务人员资源数
int waiting = 0; // 正在等待的顾客数量
Server(){
while(1){
P(customer);
叫号;
waiting--; //
V(server);
提供服务;
}
}
Customer(){
if (waiting < M){ //
取号;
waiting++; //
V(customer);
P(server);
被服务;
}
else{
离店; //
}
}
waiting 是一个临界变量,需要加上互斥锁:
信号量 customer = 0; // 顾客资源数
信号量 server = 0; // 服务人员资源数
信号量 mutex = 1;
int waiting = 0; // 正在等待的顾客数量
Server(){
while(1){
P(customer);
P(mutex); //
叫号;
waiting--;
V(mutex); //
V(server);
提供服务;
}
}
Customer(){
P(mutex); //
if (waiting < M){
取号;
waiting++;
V(mutex); //
V(customer);
P(server);
被服务;
}
else{
V(mutex);
离店;
}
}
【万能模板 3】店里有 N 名服务人员,没有顾客时服务人员忙等,有顾客时就叫号。
店里有 M 个空位。顾客到店时,需要先检查是否 M 个位置被其他顾客都占用了,若是,则离店;若否,则占用一个位置取号等待,并叫醒一名休息的服务人员(如果有),并等待叫号。
【思路】在没有顾客时,服务人员忙等,不断询问有没有顾客,所以可以在上题基础上,在服务人员进程里加入判断顾客数量的语句。如果有顾客,则按之前的操作进行;如果没有顾客,则直接进行下一轮循环。因此,对顾客资源的 P 操作就不需要了,因为 P 操作会引发阻塞或休息。如下:
信号量 customer = 0; // 顾客资源数
信号量 server = 0; // 服务人员资源数
int waiting = 0; // 正在等待的顾客数量
Server(){
while(1){
if (waiting > 0){
// P(customer); // 这句去掉,waiting 是这条 P 操作的无阻塞版本,用以实现服务人员的忙等
叫号;
waiting--;
V(server);
提供服务;
}
else{
什么也不做,进入下一轮循环;
}
}
}
Customer(){
if (waiting < M){
取号;
waiting++;
V(customer);
P(server);
被服务;
}
else{
离店;
}
}
waiting 是一个临界变量,需要加上互斥锁:
信号量 customer = 0; // 顾客资源数
信号量 server = 0; // 服务人员资源数
信号量 mutex = 1;
int waiting = 0; // 正在等待的顾客数量
Server(){
while(1){
P(mutex); //
if (waiting > 0){
// P(customer); // 这句去掉,waiting 是这条 P 操作的无阻塞版本,用以实现服务人员的忙等
叫号;
waiting--;
V(mutex); //
V(server);
提供服务;
}
else{
V(mutex); //
什么也不做,进入下一轮循环;
}
}
}
Customer(){
P(mutex); //
if (waiting < M){
取号;
waiting++;
V(mutex); //
V(customer);
P(server);
被服务;
}
else{
V(mutex); //
离店;
}
}
【万能模板 4】店里有 N 名服务人员,没有顾客时服务人员休息,有顾客时就叫号。
店里有 M 个空位。顾客到店时,需要先检查是否 M 个位置被其他顾客都占用了,若是,则离店;若否,则占用一个位置取号等待,并叫醒一名休息的服务人员(如果有),并等待叫号。
【思路】实际上,我们可以把上题的代码再改动一下,就又可以使得服务人员由忙等变为无顾客时休息了,如下:
信号量 customer = 0; // 顾客资源数
信号量 server = 0; // 服务人员资源数
int waiting = 0; // 正在等待的顾客数量
Server(){
while(1){
if (waiting > 0){
// P(customer);
叫号;
waiting--;
V(server);
提供服务;
}
else{
P(customer); // 申请一个顾客资源,若没有则阻塞或休息
什么也不做,进入下一轮循环;
}
}
}
Customer(){
if (waiting < M){
取号;
waiting++;
V(customer);
P(server);
被服务;
}
else{
离店;
}
}
waiting 是一个临界变量,需要加上互斥锁:
信号量 customer = 0; // 顾客资源数
信号量 server = 0; // 服务人员资源数
信号量 mutex = 1;
int waiting = 0; // 正在等待的顾客数量
Server(){
while(1){
P(mutex); //
if (waiting > 0){
// P(customer);
叫号;
waiting--;
V(mutex); //
V(server);
提供服务;
}
else{
V(mutex); //
P(customer); // 申请一个顾客资源,若没有则阻塞或休息
什么也不做,进入下一轮循环;
}
}
}
Customer(){
P(mutex); //
if (waiting < M){
取号;
waiting++;
V(mutex); //
V(customer);
P(server);
被服务;
}
else{
V(mutex); //
离店;
}
}
所以我们可以看到,对于存在等待上限且服务人员可休息的题目,通常存在模板 2 和模板 4 两种写法,它们都是一样的,区别就在于什么时候申请顾客资源。模板 2 在开始时申请,模板 4 在结束时申请,因此还需要在开头判断一下是否有等待的顾客。大家用哪个都可以。我们默认使用第二种模板。
大家在学习这部分内容的时候,只需把模板 1 的思路记下来即可,其余三个都是从第一个模板延伸而来。做题时,先把模板 1 默写出来,然后在此基础之上按题意进行修修补补。另外是不是有人发现了,这些代码跟服务人员的数量是不是没有关系的?因为我们模板的服务人员进程一共有 N 个,一个进程对应一个服务人员(当然也可以一个进程对应 N 个服务人员,需要额外再加一个记录服务人员的信号量),以后做这种题可以直接无视服务人员的数量,这是冗余条件。
【万能模板 5】店里有 N 名服务人员,没有顾客时服务人员休息,有顾客时就叫号。
店里有 M 个空位。顾客到店时,需要先检查是否 M 个位置被其他顾客都占用了,若是,则等待座位空余;若否,则占用一个位置取号等待,并叫醒一名休息的服务人员(如果有),并等待叫号。
【思路】由模板 1,有以下代码:
信号量 customer = 0; // 顾客资源数
信号量 server = 0; // 服务人员资源数
Server(){
while(1){
P(customer);
叫号;
V(server);
提供服务;
}
}
Customer(){
取号;
V(customer);
P(server);
被服务;
}
添加一个信号量 empty,记录店里空位的信号量。
信号量 customer = 0; // 顾客资源数
信号量 server = 0; // 服务人员资源数
信号量 empty = M;
Server(){
while(1){
P(customer);
叫号;
V(server);
V(empty); // 释放一个店里的空位
提供服务;
}
}
Customer(){
P(empty); // 店里有空位吗?若有,则空位-1,否则阻塞
取号;
V(customer);
P(server);
被服务;
}
添加互斥量:
信号量 customer = 0; // 顾客资源数
信号量 server = 0; // 服务人员资源数
信号量 empty = M;
信号量 mutex = 1;
Server(){
while(1){
P(customer);
P(mutex);
叫号;
V(mutex);
V(server);
V(empty); // 释放一个店里的空位
提供服务;
}
}
Customer(){
P(empty); // 店里有空位吗?若有,则空位-1,否则阻塞
P(mutex);
取号;
V(mutex);
V(customer);
P(server);
被服务;
}
【题目 1】理发店里有一位理发师、一把理发椅和 n 把供等候理发的顾客坐的椅子。如果没有顾客,理发师便在理发椅上睡觉;当一个顾客到来时,它必须叫醒理发师;如果理发师正在理发时又有顾客来到,那么,如果有空椅子可坐,顾客就坐下来等待,否则就离开理发店。
【思路】该题与万能模板 2 类似。
先写出基本框架:
Server(){
while(1){
P(店里是否有顾客?若有,顾客-1,否则阻塞); // 服务人员占用一个顾客资源(注意这里的阻塞已经充当服务人员休息的作用了)
叫号;
V(服务人员+1);
提供服务;
}
}
Customer(){
取号;
V(顾客+1); // (注意这里的释放已经充当唤醒服务人员的作用了)
P(店里是否有服务人员?若有,服务人员-1,否则阻塞); // 顾客占用一个服务人员资源
被服务;
}
翻译成英文:
信号量 customer = 0; // 顾客资源数
信号量 server = 0; // 服务人员资源数
Server(){
while(1){
P(customer);
叫号;
V(server);
提供服务;
}
}
Customer(){
取号;
V(customer);
P(server);
被服务;
}
加入 waiting 和判断语句:
信号量 customer = 0; // 顾客资源数
信号量 server = 0; // 服务人员资源数
int waiting = 0; // 正在等待的顾客数量
Server(){
while(1){
P(customer);
叫号;
waiting--;
V(server);
提供服务;
}
}
Customer(){
if (waiting < n){
取号;
waiting++;
V(customer);
P(server);
被服务;
}
else{
离店;
}
}
加入互斥锁,本题就结束了:
信号量 customer = 0; // 顾客资源数
信号量 server = 0; // 服务人员资源数
信号量 mutex = 1;
int waiting = 0; // 正在等待的顾客数量
Server(){
while(1){
P(customer);
P(mutex); //
叫号;
waiting--;
V(mutex); //
V(server);
提供服务;
}
}
Customer(){
P(mutex); //
if (waiting < n){
取号;
waiting++;
V(mutex); //
V(customer);
P(server);
被服务;
}
else{
V(mutex);
离店;
}
}
【题目 2】某银行提供 1 个服务窗口和 10 个供顾客等待的座位。顾客到达银行时,若有空座位,则到取号机上领取一个号,等待叫号,若没有空座位,则离开。取号机每次仅允许一位顾客使用。当营业员空闲时,通过叫号选取一位顾客,并为其服务。顾客和营业员的活动过程描述如下:
cobegin
{
process 顾客 i
{
从取号机获取一个号码;
等待叫号;
获取服务;
}
process 营业员
{
while (TRUE)
{
叫号;
为客户服务;
}
}
}
请添加必要的信号量和 P、V(或 wait()、signal())操作,实现上述过程中的互斥与同步。要求写出完整的过程,说明信号量的含义并赋初值。
【思路】这题的思路是不是和理发店是一样的?依然需要记录客户资源、记录营业员资源的信号量,除此之外还需一个记录顾客等待数量的变量 waiting。我们先不考虑 waiting,把最简单的写出来:
cobegin
{
process 顾客 i
{
从取号机获取一个号码;
V(顾客+1);
P(有营业员?若有,营业员-1,若无则阻塞);
等待叫号;
获取服务;
}
process 营业员
{
while (TRUE)
{
P(有顾客?若有,顾客-1,否则阻塞);
叫号;
V(营业员+1);
为客户服务;
}
}
}
翻译成英文:
信号量 customer = 0;
信号量 server = 0;
cobegin
{
process 顾客 i
{
从取号机获取一个号码;
V(customer);
P(server);
等待叫号;
获取服务;
}
process 营业员
{
while (TRUE)
{
P(customer);
叫号;
V(server);
为客户服务;
}
}
}
现在加入 waiting:
信号量 customer = 0;
信号量 server = 0;
int waiting = 0;
cobegin
{
process 顾客 i
{
从取号机获取一个号码;
waiting++;
V(customer);
P(server);
等待叫号;
获取服务;
}
process 营业员
{
while (TRUE)
{
P(customer);
叫号;
waiting--;
V(server);
为客户服务;
}
}
}
加入关于 waiting 的判断:
信号量 customer = 0;
信号量 server = 0;
int waiting = 0;
cobegin
{
process 顾客 i
{
if (waiting < 10)
{
从取号机获取一个号码;
waiting++;
V(customer);
P(server);
等待叫号;
获取服务;
}
else{
离开;
}
}
process 营业员
{
while (TRUE)
{
P(customer);
叫号;
waiting--;
V(server);
为客户服务;
}
}
}
做到这里,本题就算大功告成了,最后加上互斥信号量即可:
信号量 customer = 0;
信号量 server = 0;
信号量 mutex = 1;
int waiting = 0;
cobegin
{
process 顾客 i
{
P(mutex);
if (waiting < 10)
{
从取号机获取一个号码;
waiting++;
V(mutex);
V(customer);
P(server);
等待叫号;
获取服务;
}
else{
V(mutex);
离开;
}
}
process 营业员
{
while (TRUE)
{
P(customer);
P(mutex);
叫号;
waiting--;
V(mutex);
V(server);
为客户服务;
}
}
}
【题目 3】某银行提供 1 个服务窗口和 10 个供顾客等待的座位。顾客到达银行时,若有空座位,则到取号机上领取一个号,等待叫号。取号机每次仅允许一位顾客使用。当营业员空闲时,通过叫号选取一位顾客,并为其服务。顾客和营业员的活动过程描述如下:
cobegin
{
process 顾客 i
{
从取号机获取一个号码;
等待叫号;
获取服务;
}
process 营业员
{
while (TRUE)
{
叫号;
为客户服务;
}
}
}
请添加必要的信号量和 P、V(或 wait()、signal())操作,实现上述过程中的互斥与同步。要求写出完整的过程,说明信号量的含义并赋初值。
【思路】只需要把 waiting 变成信号量即可,这样它就有了阻塞的作用。我们先写中文:
cobegin
{
process 顾客 i
{
从取号机获取一个号码;
V(顾客+1);
P(有营业员?若有,营业员-1,若无则阻塞);
等待叫号;
获取服务;
}
process 营业员
{
while (TRUE)
{
P(有顾客?若有,顾客-1,否则阻塞);
叫号;
V(营业员+1);
为客户服务;
}
}
}
加入申请座位资源的中文描述:
cobegin
{
process 顾客 i
{
P(有空座位?若有,空位-1,若无则阻塞); //
从取号机获取一个号码;
V(顾客+1);
P(有营业员?若有,营业员-1,若无则阻塞);
等待叫号;
获取服务;
}
process 营业员
{
while (TRUE)
{
P(有顾客?若有,顾客-1,否则阻塞);
叫号;
V(营业员+1);
V(空座位+1); //
为客户服务;
}
}
}
翻译成英文:
信号量 customer = 0;
信号量 server = 0;
信号量 empty = 10;
cobegin
{
process 顾客 i
{
P(empty); //
从取号机获取一个号码;
V(customer);
P(server);
等待叫号;
获取服务;
}
process 营业员
{
while (TRUE)
{
P(customer);
叫号;
V(empty); //
V(server);
为客户服务;
}
}
}
加入互斥量,本题就结束了:
信号量 customer = 0;
信号量 server = 0;
信号量 empty = 10;
信号量 mutex = 1;
cobegin
{
process 顾客 i
{
P(empty);
P(mutex);
从取号机获取一个号码;
V(mutex);
V(customer);
P(server);
等待叫号;
获取服务;
}
process 营业员
{
while (TRUE)
{
P(customer);
P(mutex);
叫号;
V(mutex);
V(empty);
V(server);
为客户服务;
}
}
}