【操作系统】信号量机制及PV操作问题总结

【操作系统】信号量机制及PV操作问题总结


题型分类

​ 信号量机制应用,生产者-消费者问题,哲学家进餐问题,理发师问题,读者-写者问题,基本的同步与互斥问题。

解题的基本思路

  • 关系的确定:对于某种资源而言,哪些进程是互斥的。哪些进程又存在同步关系。然后通过信号量来实现这些关系,一个关系往往需要1个或者2个信号量来实现。
  • 确定临界资源:对于临界资源的操作往往都是 P(mutex); 临界代码;V(mutex)作为一个整体出现。互斥的信号量通常为1,代表资源数为1,同时只能供一个进程访问。
  • 需要注意的是:在多进程中为了避免出现拥有资源使用权却没有资源可以使用的死锁情况。必须先对资源信号量进行P操作,然后再对互斥信号量进行P操作。

1、前置知识 (信号量机制)

信号量机制是一种功能较强的机制,可用来解决互斥与同步问题,它只能被两个标准的原语wait(S)signal(S)访问,也可记为“P操作”和"V操作”。

(1)整型信号量

​ 一个整型信号量通常对应于一类临界资源,它是一个非负的共享整数,用来表示该类资源的数目,除了初始化外,它只能通过两个标准的原子操作wait(S)(也称作P)和signal(S)(也称作V)访问。其具体算法如下所示:

// 表示申请一个资源
wait(S) { 
    while(S <= 0); // 未遵循让权"等待原则"
    S --;
}
// 表示释放一个资源
signal(S) {
    S ++;
}
(2)记录型信号量

​ 记录型信号量机制是一种不存在“忙等”现象的进程同步机制。除了需要一个用于代表资源数目的整型变量value外,再增加一个进程链表list,用于链接所有等待该资源的进程。其算法如下所示:

typedef struct {
    int value;	//表示某类资源的数目
    struct process_control_block *list;
}semaphore;
void wait(semaphore *S) {	//申请资源
    S->value --;
    if (S->value < 0) block(S->list);
    //此时系统中已无资源可供分配,因此调用block原语自我阻塞
    //其PCB被插入信号量的等待队列S->list中
    //遵循让权等待原则
}
void signal(semaphore *S) {	//释放资源
    S->value ++;
    if (S->value <= 0) wakeup(S->list);
    //表示该类资源已经分配完毕
    //其绝对值表示系统中因申请该类资源而阻塞在S->list上的进程数目
}
(3)信号量机制的应用( 利用信号量机制实现前驱关系)

信号量也可用来描述程序之间或语句之间的前驱关系,若Pi->Pj,则可设置一个初值为0的公用信号量S,并将V(S)操作放在Pi之后,而在Pj的前面插入P(S)操作,以保证PjPi完成之后才开始执行。

(4)典型例题

【例1】 试写出相应的程序来描述如下图所示的前驱关系图(OS题解2.3.3例11)

进程前驱关系图

​ 具体算法描述如下:

semaphore a = b = c = d = e = f = g = h = 0;
S1() { S1; V(a);V(b); }
S2() { P(a); S2; V(c); V(d); }
S3() { P(b); S3; V(e); }
S4() { P(c); S4; V(f); }
S5() { P(d); S5; V(g); }
S6() { P(e); S6; V(h); }
S7() { P(f); P(g); P(h); S7; }
void main() {
    cobegin
        S1(); S2(); S3(); S4(); S5(); S6(); S7();
    coend
}

【例2]】有8个程序段p1、...、p8,它们在并发执行时有如下图所示的制约关系,试用信号量机制实现这些程序段间的同步。(OS题解2.3.3例12)

进程前驱图
​ 具体算法描述如下:

semaphore a = b = c = d = e = f = g = h = i = j = k = 0;
P1() { P1; V(a); V(b); V(c); }
P2() { P2; V(d); V(e); V(f); }
P3() { P(a); P(d); P3; V(g); }
P4() { P(b); P(e); P4; V(h); }
P5() { P(c); P(f); P5; V(i); }
P6() { P(g); P6; V(j); }
P7() { P(i); P7; V(k); }
P8() { P(j); P(h); P(k); P8; }
void main() {
    cobegin
        S1(); S2(); S3(); S4(); S5(); S6(); S7(); S8();
    coend
}

【例3】 2022-408统考真题
在这里插入图片描述
进程T1 要执行AEF,T2执行BCD,T1执行E前T2必须执行C,T2执行C必须要在T1执行A之后,因此设信号SAC, SCE 描述其同步关系,具体算法描述如下:

semaphore SAC = SCE = 0;
T1() {
    A ;
    V(SAC);
    P(SCE);
    E; F;
}
T2() {
    B;
    P(SAC);
    C;
    V(SCE);
    D;
}
void main() {
    cobegin
        T1(); T2(); 
    coend
}

【例4】分析下面给出的表达式的并行性,并用信号量进制实现该表达式的并行计算。(2020-848真题)
( 3 ∗ a ∗ b + 4 ) / ( c + d ) ( e − f ) (3 * a * b + 4) / (c + d) ^ {(e - f)} (3ab+4)/(c+d)(ef
分析由题设:
S 1 : 3 ∗ a ∗ b + 4 ; S 2 : c + d ; S 3 : e − f ; S 4 : ( c + d ) ( e − f ) S1: 3 * a * b + 4;S2: c + d; S3: e - f;S4:(c + d)^{(e -f)} S1:3ab+4;S2:c+d;S3:efS4(c+d)(ef)
S 5 : ( 3 ∗ a ∗ b + 4 ) / ( c + d ) ( e − f ) S5: (3 * a * b + 4) /(c + d)^{(e -f)} S5:(3ab+4)/(c+d)(ef)
画出前驱图如下图:
在这里插入图片描述
算法描述:

semaphore a = b = c = d = 0;
S1() { S1; V(a); }
S2() { S2; V(b); }
S3() { S3; V(c); }
S4() { P(b); P(c); V(d); }
S5() { P(a); P(d); S5; }
void main() {
    cobegin
        S1(); S2(); S3(); S4(); S5(); 
    coend
}

【例5】有四个进程A. B. C. D。进程A通过一个缓冲区不断地向进程B. C. D发送信息,A每向缓冲区送入一个信息后,必须等进程B. C. D都取走后才可以发送下一个信息,B. C. D对A送入的信息各取一次,请用信号量机制实现他们之间的正确通信。(2022-848真题

​ 分析:纯同步问题,A写完后BCD才可以读,BCD均读完后,A才可以写。

semaphore B = C = D = SBA = SCA = SDA = 0;
PA() {
    while (1) {
        向缓冲区发送一个信息;
        V(B); V(C); V(D);
        P(SBA); P(SCA); P(SDA)
    }
}
PB() {
    while (1) {
        P(B); 
        从缓冲区取出一个信息;
        V(SBA);
    }
}
PC() {
    while (1) {
        P(C); 
        从缓冲区取出一个信息;
        V(SCA);
    }
}
PD() {
    while (1) {
        P(D); 
        从缓冲区取出一个信息;
        V(SDA);
    }
}
void main() {
    cobegin
        PA(); PB(); PC(); PD();
    coend
}

【例6】有一阅览室,读者进入时必须先在一张登记表上进行登记,该表为每一座位列一表目,包括座号和读者姓名。读者离开时要消掉登记信号,阅览室中共有100个座位。请用Wait, Signal操作写出这些进程间的同步算法。

semaphore empty = 100, full = 0, mutex = 1;
void Enter() {
    P(empty);
    P(mutex);
    登记信息;
    V(mutex);
    V(full);
    就坐阅读;
}
void Exit() {
    P(full);
    P(mutex);
    消掉信息;
    V(mutex);
    V(empty);
    离开阅览室;
}

2、生产者-消费者问题

(1)问题描述

​ 桌子上有一个盘子,每次只能向其中放入一个水果。爸爸专向盘子中放苹果,妈妈专向盘子中放橘子,儿子专等吃盘子中的橘子,女儿专等吃盘子中的苹果。只有盘子为空时, 爸爸或妈妈才可向盘子中放一个水果;仅当盘子中有自己需要的水果时,儿子或女儿可以从盘子中取出。

(2)问题分析

​ 思路:问题可以抽象为两个生产者两个消费者被连接到一个大小为1的缓冲区上。

​ 信号量设置:首先将信号量plate设置互斥信号量,表示是否允许向盘子放入水果,初值为1表示允许放入,且只允许放入一个。信号量apple表示盘子中是否有苹果,初值为0表示盘子为空,不许取,apple = 1表示可以取。信号量orange表示盘子中是否有橘子, 初值为0表示盘子为空,不许取,orange = 1表示可以取。

​ 解决该问题的算法如下:

semaphore plate = 1, apple = 0, orange = 0;
Dad() {
    while (true) {
        准备一个苹果;
        P(plate);
        把苹果放入盘子
        V(apple);
    }
}
Mom() {
    while (true) {
        准备一个橘子;
        P(plate);
        把橘子放入盘子
        V(orange);
    }
}
Son() {
    while (true) {
        P(orange); 
        从盘中拿走一个橘子;
        V(plate);
        吃橘子
    }
}
Son() {
    while (true) {
        P(apple); 
        从盘中拿走一个苹果;
        V(plate);
        吃苹果;
    }
}

(3)典型例题

【例1】:桌子上有个能放得下五个水果的空盘子。爸爸不停地向盘中放苹果或橘子,儿子不停地从盘子中取出橘子享用,女儿不停地从盘子中取出苹果享用。规定三人不能同时从盘子中取放水果。试用信号量机制实现爸爸,儿子,女儿这三个循环进程之间的同步。(OS题解2.3.4例17)

semaphore empty = 5;	//初始临界区容量
semaphore orange = 0;	//盘子中橘子数目
semaphore apple = 0;	//盘子中苹果数目
semaphore mutex = 1; 	//用于互斥访问临界区(盘子)
Dad() {
    while (true) {
        P(empty);
        P(mutex);
        把水果放入盘中;
        V(mutex);
        if (放入的是橘子) V(orange);
        else V(apple);
    }
}
Son() {
    while (true) {
        P(orange); 
        P(mutex);
        从盘中取出一个橘子;
        V(mutex);
        V(empty);
        吃橘子;
    }
}
Daughter() {
    while (true) {
        P(apple); 
        P(mutex);
        从盘中取出一个苹果;
        V(mutex);
        V(empty);
        吃苹果;
    }
}

【例2】生产者-消费者问题的同步算法中,为什么颠倒生产者进程中的两个Wait
操作的次序,将导致进程死锁?(南京航空航天大学2002年硕士入学考题)

​ 答:在生产者-消费者问题中,如果将两个wait操作互换位置,或者都可能引起死锁。考虑系统中缓冲区全满时,若一生产者进程先执行了wait(mutex)操作并成功,当再执行wait(empty)时,它将因失败而进入阻塞状态,它期待消费者执行signal(mutex)来唤醒自己,在此之前,它不可能执行signal(mutex)操作,从而企图通过wait(mutex)进入自己的临界区的其他生产者和所有的消费者进程全部进入阻塞状态,从而引起系统死锁。类似地,消费者进程若先执行wait(mutex),后执行wait(full)同样可能造成死锁。

【例3】某寺庙有小和尚、老和尚若干,有一水钉,由小和尚提水入钉供老和尚饮用。水缸可容10桶水,水取自同一井中。水井径窄,每次只能容一个桶取水。水桶总数为3个。每次入缸取水仅为1桶水,且不可同时进行。试给出有关从缸取水、入水的算法描述。(24WD_OS_2.3.8_2_08)

分析:从从井水中取水并放入水缸是一个连续的动作可视为一个进程;从缸中取水可视为另一个进程。 设水井和水缸为临界资源,引入well和vat;三个水桶无论是从井中取水还是将水倒入水缸都是一次一个,应该给它们一个信号量pail,抢不到水桶的进程只好等待。水缸满时,不可以再放水,设置empty信号量来控制入水量;水缸空时,不可以取水,设置full信号量来控制。本题需要设置5个信号量来进行控制:

semaphore well = l;	//用于互斥地访问水井
semaphore vat = l;	//用于互斥地访问水缸
semaphore empty = 10;//用于表示水缸中剩余空间能容纳的水的桶数
semaphore full = 0;	//表示水缸中的水的桶数
semaphore pail = 3;	//表示有多少个水桶可以用,初值为3
OldMonk() {			//老和尚
    while (true) {
        P(full);
        P(pail);
        P(vat);
        从水缸中打一桶水;
        V(vat);
        V(empty);
        喝水;
        V(pail);
    }
}
YoungMonk() {//小和尚
    while (true) {
        P(empty);
        P(pail);
        P(well);
        从井中打一桶水;
        V(well);
        P(vat);
        将水倒入水缸中;
        V(vat);
        V(full);
        V(pail)
    }
}

【例4】设有两个生产者进程 和一个销售者进程 C, 他们共享一个无限大的仓库,生产者每次循环生产一个产品,然后入库供销售者销售;销售者每次循环从仓库中取出一个产品进行销售。如果不允许同时入库,也不允许边入库边出库;而且要求生产产品和 产品的件数满足以下关系: -n<=A 的件数- B的件数 <=m,其中 是正整数,但对仓库中 产品和 产品的件数无上述要求。请用信号量机制写出 三个进程,。请用信号量机制写出 三个进程的工作流程。(OS题解2.3.4例19)

​ 分析:同步关系:两个生产者; 生产者和消费者。临界资源:仓库。为了互斥地入库和出库,设置mutex互斥访问;为了使生产的产品满足条件设置SAB表示当前允许A生产的产品数量,其初值为 m,SBA表示当前允许B生产的产品数量,其初值为 n, 还需设置一个初值为0的资源信号量 S, 对应仓库中的产品量。具体的同步算法如下:

semaphore SAB = m, SBA = n, S = O, mutex = l;
PA() {
    while (l) {
        P(SAB);
        生产一个产品A;
        V(SBA);
        P(mutex);
        将一个产品A放入仓库中;
        V(mutex);
        V(S);
    }
} 
PB() {
    while (l) {
        P(SBA);
        生产一个产品B;
        V(SAB);
        P(mutex);
        将一个产品B放入仓库中;
        V(mutex);
        V(S);
    }
} 
PC() {
    while (l) {
        P(S);
        P(mutex);
        从仓库中取出一个产品A或者B;
        V(mutex);
        销售产品;
    }
} 
void main() {
    cobegin
        PA(); PB(); PC();
    coend
}

​ 请进一步思考:如果本题不要求仓库中产品件数满足: -n<=A 的件数-B的件数 <=m,那算法又应如何描述呢?

semaphore empty = n; full = 0, mutex = 1;
// 此时只须设置一个empty表示临界区空位,full表示临界区占有的位置即可
PA() {
    while (l) {
        P(empty);
        生产一个产品A;
        P(mutex);
        将一个产品A放入仓库中;
        V(mutex);
        V(full);
    }
} 
PB() {
    while (l) {
        P(empty);
        生产一个产品B;
        P(mutex);
        将一个产品B放入仓库中;
        V(mutex);
        V(full);
    }
} 
PC() {
    while (l) {
        P(full);
        P(mutex);
        从仓库中取出一个产品A或者B;
        V(mutex);
        销售产品;
    }
} 
void main() {
    cobegin
        PA(); PB(); PC();
    coend
}

3、哲学家进餐问题

(1)问题描述

​ 5个哲学家以交替思考、进餐的方式生活,他们坐在一张圆桌旁,桌子上有 个碗和 只筷子。当一个哲学家思考时,他与相邻的两个哲学家不会相互影响:但当他进餐时,需同时获得最靠近他的、左右两只筷子,若其中一只筷子被相邻的哲学家拿走,他就必须等待,因此,他们相互制约。

(2)问题分析

​ 该问题中,哲学家争用的临界资源是筷子,而且,每只筷子是不等效的,因此需为每只筷子分别设置一个初值为 的互斥信号量,具体的算法描述如下:

semaphore chopstick[5] = {1, 1, 1, 1, 1};
//定义信号量数组并初始化
Pi() {
    do {
        P(chopstick[i]);
        P(chopstick[(i + 1) % 5]);
        eat;
        V(chopstick[i]);
        V(chopstick[(i + 1) % 5]);
        think;
    } while (1);
}

​ 上述解法虽然可以保证互斥地使用筷子,但可能造成死锁。假如 个哲学家同时拿起各自左边的筷子,便将出现循环等待的局面,发生死锁现象。具体解决办法有以下几种。

  • 至多只允许4个哲学家同时进餐。
  • 仅当哲学家左右两边的筷子都可用时,才允许他进餐。
  • 规定奇数号哲学家先拿左边的筷子,再拿右边的筷子;而偶数号哲学家则先拿右边的筷子,再拿左边的筷子。
方法一

若至多只允许4个哲学家同时进餐:

semaphore chopstick[5] = {1, 1, 1, 1, 1};
semaphore Sm = 4; 
//设置一个初值为4的信号量限制进餐的哲学家人数不超过4
Pi() {
    do {
        P(Sm);
        P(chopstick[i]);
        P(chopstick[(i + 1) % 5]);
        eat;
        V(chopstick[i]);
        V(chopstick[(i + 1) % 5]);
        V(Sm);
        think;
    } while (1);
}
方法二

若仅当哲学家左右两边的筷子都可用时,才允许他进餐:

semaphore chopstick[5] = {1, 1, 1, 1, 1};
semaphore mutex = 1;
Pi() {
    do {
        P(mutex);	// 互斥访问拿两边筷子
        P(chopstick[i]);
        P(chopstick[(i + 1) % 5]);
        V(mutex);
        eat;
        V(chopstick[i]);
        V(chopstick[(i + 1) % 5]);
        think;
    } while (1);
}
方法三

规定奇数号哲学家先拿他左边的筷子,然后在去拿右边的筷子;而偶数号哲学家则相反。按此规定,将是1、2号哲学家竞争1号筷子;3、4号哲学家竞争3号筷子。

semaphore chopstick[5] = {1, 1, 1, 1, 1};
semaphore bowl = m;
semaphore mutex = 1;	//互斥信号量

Pi() {	//哲学家i的程序
    do {
        if (i % 2 == 0) {
        	P(chopstick[(i + 1) % n]);	//取右侧筷子
            P(chopstick[i]);			//取左侧筷子
        	eat;
        	V(chopstick[(i + 1) % n]);
        	V(chopstick[i]);
        } else {
        	P(chopstick[i]);			//取左侧筷子
        	P(chopstick[(i + 1) % n]);	//取右侧筷子
        	eat;
        	V(chopstick[i]);
        	V(chopstick[(i + 1) % n]);
        }
        think;
    } while (1);
}

(3) 典型例题

​ 【例1】有n (n>=3)名哲学家围坐在一张圆桌边,每名哲学家交替地就餐和思考。在圆桌中心有m>=1)个碗,每两名哲学家之间有一根筷子。每名哲学家必须取到一个碗和两侧的筷子后,才能就餐,进餐完毕,将碗和筷子放回原位,并继续思考.为使尽可能多的哲学家同时就餐,且防止出现死锁现象,请使用信号量的P, V操作[wait(),signal()操作]描述上述过程中的互斥与同步,并说明所用信号量及初值的含义。(2019统考真题)

semaphore chopstick[n];
semaphore bowl = m;
semaphore mutex = 1;	//互斥信号量
for (int i = 0; i < n; i++) {
    chopsticks[i] = 1;	//初始化信号量
}
bowl = min(n - 1, m);	//确保bowl <= n - 1 不死锁
// 当n - 1 >= m 碗不够多,此时最多允许m个哲学家进餐
Pi() {	//哲学家i的程序
    do {
        //P(mutex);					//互斥访问
        P(bowl);					//取碗
        P(chopstick[i]);			//取左侧筷子
        P(chopstick[(i + 1) % n]);	//取右侧筷子
        //V(mutex);					//互斥访问
        eat;
        V(chopstick[i]);
        V(chopstick[(i + 1) % n]);
        V(bowl);
        think;
    } while (1);
}

(4)拓展(死锁问题)

5个哲学家问题本质上是解决并发程序中的死锁和饥饿,可以将推广为更一般的n个进程,m个共享资源的问题。

​ 【例 2】 在哲学家就餐问题中,如果将先拿起左边的筷子的哲学家称为左撇子,而将先拿起右边的筷子的哲学家称为右撇子,请说明在同时存在左撇子和右撇子的情况下,任何就座安排都不会产生死锁。(OS题解_3.3.3例10)

​ 答:对本题,死锁产生的必要条件“循环等待“不可能成立。如果存在所有左边的哲学家等待右边的哲学家放下筷子的循环等待链,则每个哲学家肯定已获得左边的筷子,但还没得到右边的筷子,这与存在右撇子的情况不符;同样,也不可能存在相反的循环等待链。而且,系统中也不可能存在五个以下哲学家的循环等待链,因为,不相邻的哲学家之间不竞争资源。因此,不可能产生死锁。

​ 【例 3】 n个进程共享m 个同类资源,若每个进程都一要用该类资源,而且各进程对该类资源的最大需求量之和小于 m+n 。说明该系统不会因竞争该类资源而死锁。(OS题解_3.3.3例11)
在这里插入图片描述

4、读者-写者问题

(1)问题描述

​ 一个数据对象,如文件或记录,能被多个进程共享,可把那些只要求读数据对象的进程称为“读者“,其他进程则称为“写者”。显然,多个读者可同时读一个共享对象,但不允许一个写者与读者同时访问共享对象,也不允许多个写者同时访问共享对象,否则会造成数据的不一致性。这个经典同步问题就是“读者一写者问题“。

(2)问题分析

​ 若考虑读者优先(会造成写者饥饿现象),即除非有写者正在写,否则读者就毋须等待,其算法描述为

semaphore rmutex = 1;	//用于保证读者和写者互斥地访问文件
semaphore mutex = 1;	//互斥访问readcount变量
int readcount = 0;		//表示正在进行读的读者数目
writer() {				//写者进程
    while (1) {
        P(rmutex);	//互斥访问共享文件
        写者写入;
        V(rmutex);	//释放共享文件
    }
}
reader() {					//读者进程
    while(1) {
        P(mutex);
        if (readcount == 0)	//当第一个读进程读文件时
            P(rmutex);		//阻止写进程写
      	readcount ++;		//读者加一
        V(mutex);			//释放互斥变量readcount
        读者读取;
        P(mutex);
        readcount --;		//读者减一
        if (readcount == 0) //当最后一个读进程读完共享文件
            V(rmutex);		//允许写进程写 
        V(mutex);			//释放互斥变量readcount
    }
}

​ 由于只要有一个读者在读,其余读者便无需等待而可直接进行读操作,因此,仅当readcounter=0, 即尚无其他读者在读时,读者进程才需要执V(mutex)操作,而写者必须与任意其他进程互斥故每次写操作之前都必须进行 P(mutex)操作。

​ 若希望写进程优先,即当有读进程正在读共享文件时,有写进程请求访问,这时应禁止后续读进程的请求,等到己在共享文件的读进程执行完毕,立即让写进程执行,只有在无写进程执行的情况下才允许读进程再次运行。为此,增加一个信号量并在上面程序的writerreader。函数中各增加一对PV操作,就可以得到写进程优先的解决程序。

semaphore rmutex = 1;	//用于保证读者和写者互斥地访问文件
semaphore mutex = 1;	//互斥访问readcount变量
int readcount = 0;		//表示正在进行读的读者数目
semaphore w = 1;		用于实现“写优先”
writer() {				//写者进程
    while (1) {
        P(w);		//在无写进程请求时进入
        P(rmutex);	//互斥访问共享文件
        写者写入;
        V(rmutex);	//释放共享文件
        V(w);		//恢复对共享文件的访问

    }
}
reader() {					//读者进程
    while(1) {
        P(w);
        if (readcount == 0)	//当第一个读进程读文件时
            P(rmutex);		//阻止写进程写
      	readcount ++;		
        V(mutex);			
        V(w);				//恢复对共享文件的访问
        读者读取;
        P(mutex);
        readcount --;		
        if (readcount == 0) //当最后一个读进程读完共享文件
            V(rmutex);		//允许写进程写 
        V(mutex);			
    }
}

(3)典型例题

​ 【例1】请用信号量解决以下的“过独木桥”问题:同一方向的行人可连续过桥,当某一方向有人过桥时,另一方向的行人必须等待;当某一方向无人过桥时,另一方向的行人可以过桥。(OS题解2.3.4例22)

​ 答:独木桥问题是读者—写者问题的一个变形,同一个方向的行人可以同时过桥,这相当于读者可以同时读。因此,可将两个方向的行人看做是两类不同的读者,同类读者(行人)可以同时读(过桥),但不同类读者(行人)之间必须互斥地读(过桥)。具体算法实现如下:

int countA = 0, countB = 0;	//AB两个方向的行人数量
seamphore brige = 1;		//互斥访问
seamphore mutexA = 1, mutexB = 1;
//分别用来实现对 countA、 countB的互斥共享
PA() {
    P(mutexA);
    if (countA == 0) 
        P(brige);
    countA ++;
    V(mutexA);
    过桥;
    P(mutexA);
    countA --;
    if (countA == 0)
        V(brige);
    V(mutexA);
}
PB() {
    P(mutexB);
    if (countA == 0)
        P(brige);
    countB ++;
    V(mutexB);
    过桥;
    P(mutexB);
    countB --;
    if (countB == 0)
        V(brige);
    V(mutexB);
}

【例2】一座小桥横跨南北两岸,南侧桥段和北侧桥段较窄只能通过一人,桥中央一处宽敞,允许两个人通过或歇息。南侧桥段和北侧桥段在任意时刻只允许一人过桥。请写出从北岸到南岸过桥的同步算法。(2023-848真题)

semaphore mutex_North = 0, mutex_Sourth = 0; 
//两侧最多容纳一人,相当于互斥访问
int num = 2; 		//桥中间最多容纳两人
Procedure_North() {	//北边的人(由北到南)
    while (1) {
        P(num);
        P(mutex_North);
        通过桥北侧到达桥中间;
        V(mutex_North);
        P(mutex_Sourth);
        通过桥南侧;
        V(mutex_Sourth);
        V(num);
    }
}
Procedure_Sourth() { //南边的人(由南到北)
    while (1) {
        P(num);
        P(mutex_Sourth);
        通过桥南侧到达桥中间;
        V(mutex_Sourth);
        P(mutex_North);
        通过桥北侧;
        V(mutex_North);
        V(num);
    }
}
void main() {
    cobegin
        Procedure_North(); Procedure_Sourth();
    coend
}

5、理发师问题

(1)问题描述

​ 理发店理有一位理发师、一把理发椅和n把供等候理发的顾客坐的椅子,如果没有顾客,理发师便在理发椅上睡觉。一个顾客到来时,它必须叫醒理发师。如果理发师正在理发时又有顾客来到,则如果有空椅子可坐,就坐下来等待,否则就离开。

(2)问题分析

控制变量waiting用来记录等候理发的顾客数,初值均为0;
信号量customers用来记录等候理发的顾客数,并用作阻塞理发师进程,初值为0;
信号量barbers用来记录正在等候顾客的理发师数,并用作阻塞顾客进程,初值为0 信号量mutex用于互斥,初值为1

int waiting=0 ; 	//等候理发的顾客数
int chairs = n ; 	//为顾客准备的椅子数
semaphore customers = 0, barbers = 0,mutex = 1;
barber() {
    while (1) {
        P(mutex);
        if (waiting > 0) {	//若有顾客
            waiting --;		//顾客数-1
            V(mutex);		//开放临界区
            V(barbers);		//理发师去为一个顾客理发
            正在理发;
        } else {
            V(mutex);
            P(customer);	//没有顾客 申请睡觉
        }
    }
}
customer() {
    P(mutex);		// 进程互斥
    /*
    //若人太多离开
    if (waiting >= n) {
        V(mutex);	// 人太多离开
        return ;
    }
    */
    waiting ++;		 //等候顾客数+1
    V(mutex);  		//开放临界区
    V(customers);	//唤醒理发师
    P(barbers);	 	//申请服务
    等待服务;
}

思考:有3个理发师,3把理发椅子,n把供等候理发的顾客坐的椅子.由于有3位理发师,所以一次同时可以为三个顾客服务,设置信号量max_capacity, 用于表示空闲椅子的数量,初值为n.信号量barber_chair表示空闲理发师(椅) 的数量,初值为3;信号量cust_ready,finished,leave_b_chair分别表示是否有顾客到来,理发完成,离开理发椅,它们的初值都为0;

semaphore max_capacity = n;
semaphore barber_chair = 3;
semaphore cust_ready = 0, finished = 0; leave_b_chair = 0;
barber() {
    while (1) {
        p(cust_ready);
        理发;
    }
}
customer() {
    P(max_capacity);//是否有空闲椅子
    进入店里;
    P(barber_chair);//是否有空闲的理发椅
    坐在理发椅上;
    V(cust_ready);   //唤醒理发师
    P(finished);	// 理发
    离开理发椅;
    V(leave_b_chari);
    离开店;
    V(max_capacity);
}

(3)典型例题

​ 【例1】有一个理发师,一把理发椅和n把供等候理发的顾客坐的椅子,若没有顾客,则理发师睡觉,当一个顾客到来时,必须唤醒理发师进行理发, 若理发师正在理发,又有顾客到来,则若有空椅子可坐就坐下来等,若没有空椅子就离开。(电子科技大学2000)

​ 问题分析:需要设置一个信号量barber,初值为0,用于控制理发师和顾客之间的同步关系.还需要设置一个信号量customer,初值为0,用于离开顾客与等候顾客之间的同步控制,为了记录等候的顾客数, 应该设置一个计数器count,初值为0.当一个顾客到达时,需要在count上做加1操作,并根据count值的不同分别采取不同的动作,当顾客离开时,要对count上做减1操作,并根据count值的不同分别采取不同的动作;由于count是共享变量,因此要互斥使用,为此设置一个互斥信号量mutex;

semaphore barber = 0, customer = 0,  mutex = 1;
int count = 0;
barber() {
    while (1) {
        P(customer);	//请求被唤醒
        P(mutex);	    //互斥访问
        count --;
        V(barber);		//理发
        V(mutex);
        理发;
    }
}
customer() {
    P(mutex);
    if (count < n) {
        count ++;
        V(customer);	//唤醒理发师
        P(barber);		//申请理发
        理发;
    } else {
        V(mutex);
        离开;
    }
}

​ 【例2】嗜睡的理发师问题:一个理发店由一个有 张沙发的等候室和一个放有一张理发椅的理发室组成。没有顾客要理发时,理发师便去睡觉。当一个顾客走进理发店时,如果所有的沙发都已被占用,他便离开理发店;否则,如果理发师正在为其他顾客理发,则该顾客就找一张空沙发坐下等待;如果理发师因无顾客正在睡觉,则由新到的顾客唤醒理发师为其理发。在理发完成后,顾客必须付费,直到理发师收费后才能离开理发店。试用信号量实现这一同步问题。(OS_题解2.3.4例20)

​ 问题分析:为解决上述问题,需设置一个整型变量 count用来对占用沙发的顾客进行计数,并需设置 个信号量,其中, mutex 用来实现顾客进程对 count变量的互斥访问,其初值1; empty表示是否有空闲的理发椅,其初值为 1; full 表示理发椅上是否坐有等待理发的顾客,其初值为 0; payment用来等待付费,其初值为 0; receipt用来等待收费,其初值为0 。

int count = 0;
semaphore mutex = 1, empty = 1, full = 0;
semaphore payment = 0, receipt = 0;
Guest() {
    P(mutex);
    if (count >= N) {	//沙发已被全部占用
        V(mutex);
        离开理发店;
        return ;
    }
    count ++;
    V(mutex);
    在沙发就坐;
    P(empty);	//等待理发椅变空
    离开沙发到理发椅;
    P(mutex);
    count --;
    V(mutex);
    V(full);
    理发;
    付费;
    V(payment); //通知理发师付费
    V(receipt);	//等理发师收费
    V(empty);	//离开理发椅
    离开理发店;
}
Barber() {
    while (1) {
        P(full);	 //如没顾客就在此睡觉
        替顾客理发;
        P(payment);  //等顾客付费
        收费;
        V(receipt); //通知顾客收费完成
    }
}

6、经典题目分析

(1)银行排队问题
问题描述:

​ 银行有n个柜员,每个顾客进入银行后先取一个号,并且等着叫号,当一个柜员空闲后,就叫下一个号.

问题分析:

​ 将顾客号码排成一个队列,顾客进入银行领取号码后,将号码由队尾插入;柜员空闲时,从队首取得顾客号码,并且为这个顾客服务,由于队列为若干进程共享, 所以需要互斥.柜员空闲时,若有顾客,就叫下一个顾客为之服务.因此,需要设置一个信号量来记录等待服务的顾客数. 算法实现如下:

semaphore mutex = 1;	//互斥访问
semaphore count = 0;	//记录等待的顾客数
Process customer() {
    取号;
    P(mutex);
    进入队列;
    V(mutex);
    V(count);
}
Process servers_i() {
    while (1) {
        P(count);
        P(mutex);
        从队列中取下一个号码;
        V(mutex);
        为该号码持有者提供服务;
    }
}
典型例题:

​ 【例1】 某银行在两个门口分别放置了排队取号机,客户进门时在取号机上按取号键,取号机打印输出含有排队序号和前面等待人数的纸条给客户。若干柜员每人桌面放有一台已设好窗口号的叫号机,按下叫号键若有客户等待就播放排在最前面的客户的号码和窗口号,没有客户则程序等待。请用信号量机制模拟叫号系统的功能。(10 分)(2021-848)

​ 分析:互斥资源:叫号机; 同步:排队取号与等待叫号。

semaphore mutex_num = 1, mutex_call = 1;
semaphore service = 0, customer = 0;
//customer表示当前等待服务的客户人数
//service用来处理取号和叫号的同步
int current_num = 0;	 	//排队序号
customer_i() {
    P(mutex_num);			//互斥取号
    current_num ++; 		//取号;
    V(mutex_num);
    //取号机打印信息给顾客;
    cout << current_num << ' ' << customer << endl; 
    V(customer); //顾客数加一
    P(service);	 //等待叫号
    办理业务;
    离开;
}
server_i() {
    while (1) {
        P(mutex_call);
        按下叫号键;
        if (waiting > 0) {
            P(customer);	//顾客数减一
            V(service);		//叫号
            cout << current_num << " " << server_i << endl;
        } else {
            等待;
        }
        V(mutex_call);
    }
}

​ 【例2】某银行提供1个服务窗口和10个供顾客等待的座位。顾客到达银行时, 若有空座位,则到取号机上领取一个号,等待叫号。取号机每次仅允许一位顾客使用.当营业员空闲时,通过叫号选取一位顾客,并为其服务。请用信号量机制实现其同步算法。(2011统考)

semaphore mutex = 1;	//互斥资源-取号机
semaphore empty = 10, full = 0;
semaphore service = 0;	//等待叫号
process customer_i() {
    P(empty);
    P(mutex);
    取号;
    V(mutex);
    V(full);
    P(service);
}
process server() {
    while (1) {
        P(full);
        V(empty);	//顾客离开座位
        P(mutex);
        叫号;
        V(mutex);
        V(service);	//叫号
        为顾客服务;
    }
}
(2)帐户问题

​ 两人公用一个账号,每次限存或取10元;(南京大学 2000 年试题 )

semaphore mutex = 1;	//存钱与取钱是并发的因此要互斥访问
int amount = 0;
Save() {
    int m1 = 0;
    P(mutex);
    m1 = amout;
    m1 = m1 + 10;
    amout = m1;
    V(mutex);
}
Take() {
    int m2 = 0;
    P(mutex);
    m2 = amout;
    m2 = m2 - 10;
    amout = m2;
    V(mutex);
}
(3)公交车问题

(华中理工大学 1999,哈工大2000年研究生入学试题 ) 设公共汽车上,有一位司机和一位售票员,它们的活动如下:

司机: 启动车辆; 正常行车; 到站停车;

售票员: 关车门; 售票; 开车门;

请分析司机与售票员之间的同步关系, 如何用信号量和 PV操作实现它们的同步。

​ 分析:在汽车行驶过程中,驾驶员活动与售票员活动之间的同步关系为:售票员关车门后,向驾驶员发开车信号,驾驶员接到开车信号后启动车辆,在汽车正常行驶过程中售票员售票,到站时驾驶员停车,售票员在车停后开门让乘客上下车。因此,驾驶员启动车辆的动作必须与售票员关车门的动作同步;售票员开车门的动作也必须与驾驶员停车同步。应设置两个信号量S1,S2S1表示是否允许驾驶员启动汽车(初值为0); S2表示是否允许售票员开门(初值为0)。

semaphore S1 = S2 = 0;
//S1表示是否允许司机启动汽车
//S2表示是否允许售票员开门
Driver() {
    while (1) {
        P(S1);
        启动车辆;
        正常行车;
        到站停车;
        V(S2);
    }
}
Driver() {
    while (1) {
        关车门;
        V(S1);
        售票;
        P(S2);
        开车门;
    }
}
(4)安全岛问题

​ 在南开大学至天津大学间有一条弯曲的路,每次只允许一辆自行车通过,但中间有小的安全岛M(同时允许两辆车),可供两辆车在已进入两端小车错车,设计算法并使用P,V操作实现。

在这里插入图片描述
​ 问题分析:由于安全岛M仅仅允许两辆车停留,本应该作为临界资源而要设置信号量, 但根据题意,任意时刻进入安全岛的车不会超过两辆(两个方向最多各有一辆), 因此,不需要为M设置信号量,在路口s和路口t都需要设置信号量,以控制来自两个方向的车对路口资源的争夺.这两个信号量的初值都是1.此外,由于从s到t的一段路只允许一辆车通过,所以还需要设置另外的信号量用于控制,由于M的存在,可以为两端的小路分别设置一个互斥信号量.

semaphore T2N = 1;//从T到N的互斥信号量
semaphore N2T = 1;//从N到T的互斥信号量
semaphore L = l;//经过L路段的互斥信号量
semaphore k = 1;//经过K路段的互斥信号量
Procedure Bike T2N {
    P(T2N);
    P(L);
    go T to L;
    go into M; 
    V(L);
    P(k);
    go K to N;
    V(k);
    V(T2N);
}
Procedure Bike N2T(
    P(N2T);
    p(k);
    go N to k;
    go into M;
    V(k);
    P(L);
    go L to T;
    V(L);
    V(N2T);
}
(5)珍珑棋局问题

问题描述:在一个盒子里,混装了数量相等的黑白围棋子·现在用自动分拣系统把黑子、白子分开,设分拣系统有二个进程P1 和P2 ,其中P1 拣白子;P2 拣黑子。规定每个进程每次拣一子;当一个进程在拣时,不允许另一个进程去拣;当一个进程拣了一子时,必须让另一个进程去拣.试写出两进程P1 和P2 能并发正确执行的程序。
问题分析:大家熟悉了生产-消费问题(PC),这个问题很简单。题目较为新颖,但是本质非常简单即:生产-消费问题的简化或者说是两个进程的简单同步问题。答案如下:

//设信号量s1 和s2 分别表示可拣白子和黑子;
semaphore S1 = 1, S2 = 0;

Process P1() {
	while (1) {
		P(S1);
		pick the white;
		V(S2);
	}
}

Process P2() {
	while (1) {
		P(S2);
		pick the back;
		V(S1);
	}
}
(6)过桥问题 (华中科技大学2000)

如图所示:
在这里插入图片描述

(1) 桥每次只能有一辆车通过。
(2) 不允许两车交会,但允许同方向的多辆车依次通过。

semaphore mutex = 1;
// 南边来的车
Process Scar() {
	Come;
	P(mutex);
	过桥;
	V(mutex);
	go;
}

//北边来的车
Process Ncar() {
	Come;
	P(mutex);
	过桥;
	V(mutex);
	go;
}

semaphore Smutex = 1, Nmutex = 1, mutex = 1;
int ScarCount = 0, NcarCount = 0;

Process Scar_i() {
	P(Smutex);
	if (ScarCount == 0) 
		P(mutex);
	ScarCount ++;
	V(Smutex);
	过桥;
	P(Smutex);
	ScarCount --;
	if (ScarCount == 0) 
		V(mutex);
	V(Smutex);
}

Process Ncar_j() {
	P(Nmutex);
	if (NcarCount == 0) 
		P(mutex);
	NcarCount ++;
	V(Nmutex);
	过桥;
	P(Nmutex);
	NcarCount --;
	if (NcarCount == 0) 
		V(mutex);
	V(Nmutex);
}

参考书目

  1. 《计算机操作系统(第4版)》(汤小丹)
  2. 《计算机操作系统(第4版)》学习指导与题解(梁红兵,汤小丹编著)
  3. 《2024王道计算机操作系统考研复习指导》
  4. 《操作系统之PV金典第二版》
  • 10
    点赞
  • 97
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值