🏷️ 知识点:同步问题设计
有 n(n ≥ 3)位哲学家围坐在一张圆桌边,每位哲学家交替地就餐和思考。在圆桌中心有 m(m ≥ 1)个碗,每两位哲学家之间有一根筷子。每位哲学家必须取到一个碗和两侧的筷子后,才能就餐,进餐完毕,将碗和筷子放回原位,并继续思考。为使尽可能多的哲学家同时就餐,且防止出现死锁现象,请使用信号量的 P、V 操作(wait()、signal() 操作)描述上述过程中的互斥与同步,并说明所用信号量及初值的含义。
[tag_link]
回顾传统的哲学家就餐问题,假设餐桌上有 n 个哲学家、根筷子,那么可以用这种方法避免死锁:限制至多允许 n-1 个哲学家同时“抢”筷子,那么至少会有 1 个哲学家可以获得两根筷子并顺利进餐,于是不可能发生死锁的情况。本题可以用碗这个限制资源来避免死锁:当碗的数量 m 小于哲学家的数量 n 时,可以直接让碗的资源量等于 m,确保不会出现所有哲学家都拿一侧筷子而无限等待另一侧筷子进而造成死锁的情况;当碗的数量大于等于哲学家的数量时,为了让碗起到同样的限制效果,我们让碗的资源量等于 -1,这样就能保证最多只有 n-1 个哲学家同时进餐,所以得到碗的资源量为 min{n-1, m}。在进 PV 操作时,碗的资源量起限制哲学家取筷子的作用,所以需要先对碗的资源量进行 P 操作。具体过程如下:
// 限制哲学家能同时拿到盘子的数量
semaphore fork[n] = {1};
// 并发盘子数量 < n
semaphore plate = min(m, n-1);
philosopher(int i) {
while (1) {
think();
P(plate);
P(fork[i]);
P(fork[(i + 1) % n]);
eat();
V(fork[i]);
V(fork[(i + 1) % n]);
V(plate);
}
}
某银行提供 1 个服务窗口和 10 个顾客等待座位。顾客到达银行时,若有空座位,则到取号机领取一个号,等待叫号。取号机每次仅允许一位顾客使用。当营业员空闲时,通过叫号选取一位顾客,并为其服务。顾客和营业员的活动过程描述如下:
cobegin
{
process 顾客 i
{
从取号机获得一个号码;
等待叫号;
获得服务;
}
process 营业员
{
while (TRUE) {
叫号;
为顾客服务;
}
}
}coend
请添加必要的信号量和 P、V(或 wait()、signal())操作实现上述过程的互斥和同步。要求写出完整的过程,说明信号量的含义并赋初值。
[tag_link]
1)互斥资源:取号机(一次只一位顾客领号),因此设一个互斥信号量 mutex。
2)同步问题:顾客需要获得空座位等待叫号,当营业员空闲时,将选取一位顾客并为其服务。空座位的有、无影响等待顾客数量,顾客的有、无决定了营业员是否能开始服务,故分别设置信号量 empty 和 full 来实现这一同步关系。另外,顾客获得空座位后,需要等待叫号和被服务。这样,顾客与营业员就服务何时开始又构成了一个同步关系,定义信号量 service 来完成这一同步过程。
semaphore empty = 10; // 空座位的数量
semaphore mutex = 1; // 互斥使用取号机
semaphore full = 0; // 已占座位的数量
semaphore service = 0; // 等待叫号
process 顾客 i {
P(empty); // 等空位
P(mutex); // 申请使用取号机
从取号机上取号;
V(mutex); // 取号完毕
V(fu11); // 通知营业员有新顾客
P(service); // 等待营业员叫号
接受服务;
}
process 营业员 {
while(True) (
P(fu11); // 没有顾客则休息
叫号;
V(empty); // 离开座位
V(service); // 叫号
为顾客服务;
}
}
某博物馆最多可以容纳 500 人同时参观,有一个出入口,该出入口一次仅允许一个人通过。参观者的活动描述如下:
cobegin
参观者进程 i:
{
...
进门;
...
参观;
...
出门;
...
}
coend
请添加必要的信号量和 P、V(或 wait()、signal())操作,以实现上述过程中的互斥与同步。要求写出完整的过程,说明信号量的含义并赋初值。
出入口一次仅允许一个人通过,设置互斥信号量 mutex, 初值为 1。博物馆最多可同时容纳 500 人,故设置信号量 empty, 初值为 500。
semaphore empty = 500;
semaphore mutex = 1;
visitor() {
P(empty);
P(mutex);
进门;
V(mutex);
参观;
P(mutex);
出门;
V(mutex);
V(empty);
}
【评分说明】①信号量初值给 1 分,说明含义给 1 分,两个信号量的初值和含义共 4 分。②对 mutex 的 P、V 操作正确给 2 分。③对 empty 的 P、V 操作正确给 1 分。④其他答案,参照①~③的标准给分。
[tag_link]
有 A、B 两人通过信箱进行辩论,每个人都从自己的信箱中取得对方的问题,将答案和向对方提出的新问题组成一个邮件放人对方的信箱中。假设 A 的信箱最多放M个邮件,B 的信箱最多放N个邮件。初始时 A 的信箱中有x个邮件 (0<x<M),B 的的信箱中有y个邮件 (0<y<N)。辩论者每取出一个邮件,邮件数减 1。A 和 B 两人的操作过程描述如下:
A {
while (true) {
从 A 的信箱中取出一个邮件;
回答问题并提出一个新问题;
将新邮件放入 B 的信箱;
}
}
B {
while (true) {
从 B 的信箱中取出一个邮件;
回答问题并提出一个新问题;
将新邮件放入 A 的信箱;
}
}
当信箱不为空时,辩论者才能从信箱中取邮件,否则等待。当信箱不满时,辩论者才能将新邮件放入信箱,否则等待。请添加必要的信号量和 P、V(或 wait、signal)操作,以实现上述过程的同步。要求写出完整的过程,并说明信号量的含义和初值。
semaphore A_full = x; // A 信箱中已有的邮件个数
semaphore A_empty = M - x; // A 信箱还可以放多少个邮件
semaphore B_full = y; // B 信箱中已有的邮件个数
semaphore B_empty = N - y; // B 信箱中还能放多少个邮件
semaphore A_mutex = 1; // 互斥访问 A 信箱
semaphore B_mutex = 1; // 互斥访问 B 信箱
A() {
while (1) {
P(A_full);
P(A_mutex);
从A信箱中取出一个邮件;
V(A_mutex);
V(A_empty);
回答问题并提出新问题;
P(B_empty);
P(B_mutex);
将信件放入B邮箱;
V(B_mutex);
V(B_full);
}
}
B() {
while (1) {
P(B_full);
P(B_mutex);
从B信箱中取出一个邮件;
V(B_mutex);
V(B_empty);
回答问题并提出新问题;
P(A_empty);
P(A_mutex);
将信件放入A邮箱;
V(A_mutex);
V(A_full);
}
}
【评分说明】
1)每对信号量的定义及初值正确,给分。
2)每个互斥信号量的 P、V 操作使用正确,各给分。
3)每个同步信号量的 P、V 操作使用正确,各给分。
4)其他答案酌情给分。
现有 5 个操作 A、B、C、D 和 E,操作 C 必须在 A 和 B 完成后执行,操作 E 必须在 C 和 D 完成后执行,请使用信号量的 wait()、signal() 操作(P、V 操作)描述上述操作之间的同步关系,并说明所用信号量及其初值。
[tag_link]
本题要求实现操作的先后顺序,没有互斥关系,是一个简单的同步问题。本题虽然有 5 个操作,但是只有 4 个同步关系,因此分别设置信号量 SAC、SBC、SCE 和 SDE 对应 4 个同步关系。
semaphore SAC = 0; // 实现 A 是 C 的前驱关系
semaphore SBC = 0; // 实现 B 是 C 的前驱关系
semaphore SCE = 0; // 实现 C 是 E 的前驱关系
semaphore SDE = 0; // 实现 D 是 E 的前驱关系
A() {
操作A;
V(SAC);
}
B() {
操作B;
V(SBC);
}
C() {
P(SAC);
P(SBC);
操作C;
V(SCE);
}
D() {
操作D;
V(SDE);
}
E() {
P(SCE);
P(SDE);
操作E;
}
下表给出了整型信号量 S 的 wait() 和 signal() 操作的功能描述,以及采用开/关中断指令实现信号量操作互斥的两种方法。
功能描述
Semaphore S;
wait(S) {
while (S <= 0);
S = S-1;
}
signal(S) {
S = S+1;
}
方法 1
Semaphore S;
wait(S) {
关中断;
while(S <= 0);
S = S-1;
开中断;
}
signal(S) {
关中断;
S = S+1;
开中断;
}
方法 2
Semaphore S;
wait(S) {
关中断;
while(S <= 0) {
开中断;
关中断;
}
S = S-1;
开中断;
}
signal(S) {
关中断;
S = S+1;
开中断;
}
请回答下列问题。
(1) 为什么在 wait() 和 signal() 操作中对信号量 S 的访问必须互斥执行?
(2) 分别说明方法 1 和方法 2 是否正确。若不正确,请说明理由。
(3) 用户程序能否使用开/关中断指令实现临界区互斥?为什么?
[tag_link]
1)信号量S是能被多个进程共享的变量,多个进程都可通过 wait() 和 signal() 对S进行读、写操作。所以,wait() 和 signal() 操作中对 S 的访问必须是互斥的。
2)方法 1 错误。在 wait() 中,当S≤0时,关中断后,其他进程无法修改 S 的值,while语句陷入死循环。方法 2 正确。方法 2 在循环体中有一个开中断操作,这样就可以使其他进程修改S的值,从而避免 while 语句陷入死循环。
3)用户程序不能使用开/关中断指令实现临界区互斥。因为开中断和关中断指令都是特权指令,不能在用户态下执行,只能在内核态下执行。
三个人一起植树,甲挖坑,乙放树苗入坑并填土,丙负责为新种树苗浇水。步骤依次为:挖树坑,放树苗,填土和浇水。现在有铁锹和水桶各一个,铁锹用于挖树坑,填土。水桶用于浇水。当树坑数量小于 3 时,甲才可以挖树坑。设初始坑 = 0,铁锹水桶均可用,定义尽可能少的信号量,用 wait() 和 signal() 操作描述植树过程中三人的同步互斥关系,并说明所用信号量的作用及其初值。
[tag_link]
这题是一个近似于流水线的结构,其过程为:挖树坑(甲)→ 放树苗、填土(乙)→ 浇水(丙)。不过甲最多可以同时挖三个树苗,也就是说不允许同时存在 4 个未被乙使用的树坑,这是比较复杂的一点。实现甲和乙之间的同步需要使用到 pits 和 empty 这两个信号量,同时还需要一个 water 信号量来实现乙和丁的同步,代码实现如下:
semaphore mutex = 1; // 对铁锹的使用需要互斥
semaphore pits = 3; // 甲还能挖洞的数量
sempahore empty = 0; // 可以使用的树坑数量
sempahore water = 0; // 需要浇水的水苗数量
甲() {
while (1) {
wait(pits); // 最多只能挖三个未被乙使用的坑
wait(mutex); // 占用铁锹
挖树坑;
signal(mutex); // 释放铁锹
signal(empty); // 通知乙可以放树苗和填土了
}
}
乙() {
while (1) {
wait(empty); // 等待到有树坑为止
wait(mutex); // 占用铁锹
放树苗、填土;
signal(mutex); // 释放铁锹
signal(pits); // 通知甲可以继续挖坑了
signal(water); // 通知丙可以浇水了
}
}
丙() {
while (1) {
wait(water);
浇水;
}
}
(8分)某进程的两个线程T1和T2 并发执行A B C D E 和F 共6个操作,其中T1 执行A E和F, T2执行 B C 和 D。题46图表示上述6个操作的执行顺序所必须满足的约束:C 在A 和B 完成后执行,D 和E 在C 完成后执行,F 在E 完成后执行。请使用信号量的walt() 、slgnal( 操作描述T1 和T2 之间的同 步关系,并说明所用信号量的作用及其初值。
[tag_link]
[tag_link]
进程 T1 要依次执行 A、E、F。进程 T2 要执行 B、C、D。由图可知,T2 执行 C 必须在 T1 执行完 A 之后;T1 执行 E 必须在 T2 执行完 C 之后。因此,有两对同步关系。信号量的定义 和同步关系的描述如下:
semaphore AC = 0 ;
semaphore CE = 0 ;
T1 () {
A ;
signal ( AC );
wait ( CE );
E ;
F ;
}
T2 () {
B ;
wait ( AC );
C ;
signal ( CE );
D ;
}
(8分)系统中有多个生产者进程和多个消费者进程,共享一个能存放1000件产品的环形缓冲区(初 始为空)。当缓冲区未满时,生产者进程可以放入其生产的一件产品,否则等待;当缓冲区未空时,消费 者进程可以从缓冲区取走一件产品,否则等待。要求一个消费者进程从缓冲区连续取出10件产品后,其
他消费者进程才可以取产品。请使用信号量P 、V(wait(),signal() 操作实现进程间的互斥与同步,要求 写出完整的过程,并说明所用信号量的含义和初值。
[tag_link]
这是典型的生产者和消费者问题,只对典型问题加了一个条件,只需在标准模型上新加一个信号量,即可完成指定要求。
设置四个变量 consumer_mutex、buffer_mutex、empty 和 full:consumer_mutex 用于控制一个消费者进程一个周期(10 次)内对于缓冲区的控制,初值为 1;buffer_mutex 用于进程单次互斥的访问缓冲区,初值为 1;empty 代表缓冲区的空位数,初值为 0;full 代表缓冲区的产品数,初值为 1000。
semaphore consumer_mutex = 1;
semaphore buffer_mutex = 1;
semaphore full = 0;
semaphore empty = 1000;
Consumer() {
while (1) {
P(consumer_mutex);
for (int i = 0; i < 10; i++) {
P(full);
P(buffer_mutex);
// 从缓冲区取出产品
V(buffer_mutex);
V(empty);
// 消费产品
}
V(consumer_mutex);
}
}
Producer() {
while (1) {
P(empty);
// 生产产品
P(buffer_mutex);
// 将产品放入缓冲区
V(buffer_mutex);
V(full);
}
}
【评分说明】 ①信号量的初值和含义都正确给 2 分。 ②生产者之间的互斥操作正确给 1 分;生产者与消费者之间的同步操作正确给 2 分;消费者之间互斥操作正确给 1 分。 ③控制消费者连续取产品数量正确给 2 分。 ④仅给出经典生产者 - 消费者问题的信号量定义和伪代码描述最多给 3 分。 ⑤若考生将题意理解成缓冲区至少有 10 件产品,消费者才能开始取,其他均正确,得 6 分。 ⑥部分完全正确,酌情给分。