G
N
I
D
A
O
L

【操作系统-进程】PV操作——理发师问题

理发师问题万能模板

理发师问题与生产者消费者问题不同,生产者消费者问题是“生产-消费”问题,理发师问题是“服务-被服务”的问题。然而,这两个问题从根本上来说思路是一样的,下面请大家仔细研究这几个模板。

万能模板 1——无等待上限,服务人员可休息

【万能模板 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——有等待上限,服务人员可休息(1)

【万能模板 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——有等待上限,服务人员忙等

【万能模板 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——有等待上限,服务人员可休息(2)

【万能模板 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——无等待上限,服务人员可休息

【万能模板 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:理发店

【题目 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:银行(店满离开)

【题目 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:银行(店满等待)

【题目 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);
            为客户服务;
        }
    }
}
posted @ 2022-10-13 16:01  漫舞八月(Mount256)  阅读(1594)  评论(2编辑  收藏  举报