56. B

2026 旧版 · 非 2027 主源 · 原书检索 · 文字用于定位,公式与图表请回原页核验 · 本次导入快照

## 56. B

仔细阅读两个线程代码可知,thread1 和 thread2 均是对 x 进行加 1 操作,x 的初始值为 0,若要使得最终 x = 2,只能先执行完 thread1 再执行 thread2,或先执行完 thread2 再执行 thread1,因此仅有 2 种可能。

## 57. D

“条件变量”是管程内部说明和使用的一种特殊变量,其作用类似于信号量机制中的“信号量”,都用于实现进程同步。需要注意的是,在同一时刻,管程中只能有一个进程在执行。若进程 A 执行了 x.wait()操作,则该进程会阻塞,并挂到条件变量 x 对应的阻塞队列上。这样,管程的使用权被释放,就可以有另一个进程进入管程。若进程 B 执行了 x.signal()操作,则会唤醒 x 对应的阻塞队列的队首进程。只有一个进程要离开管程时才能调用 signal()操作。

## 58. C

硬件方法实现进程同步时不能实现让权等待,选项 B 和 D 错误;Peterson 算法满足有限等待但不满足让权等待,选项 A 错误;记录型信号量由于引入阻塞机制,消除了不让权等待的情况,选项 C 正确。

## 59. C

实现临界区互斥需满足多个准则。“忙则等待”准则,即两个进程不能同时访问临界区,选项 I 正确。“空闲让进”准则,若临界区空闲,则允许其他进程访问,选项 II 正确。“有限等待”准则,即进程应该在有限时间内访问临界区,选项 III 正确。选项 I、II 和 III 是互斥机制必须遵循的原则。选项 IV 是“让权等待”准则,不一定非得实现,如皮特森算法。

## 二、综合应用题

## 01.【解答】

$\mathrm{P}_{1}$ 和 $\mathrm{P}_{2}$ 两个并发进程的执行结果是不确定的,它们都对同一变量 X 进程操作,X 是一个临界资源,而没有进行保护。例如:

1)若先执行完 $\mathrm{P}_{1}$ 再执行 $\mathrm{P}_{2}$,结果是 $x = 0, y = 1, z = 1, t = 2, u = 2$。

2)若先执行 $\mathrm{P}_{1}$ 到“$\mathrm{x} = 1$”,然后一个中断去执行完 $\mathrm{P}_{2}$,再一个中断回来执行完 $\mathrm{P}_{1}$,结果是


<!-- page: 144 -->

x = 0, y = 0, z = 0, t = 2, u = 2。
显然,两次执行结果不同,所以这两个并发进程不能正确运行。可将这个程序改为:
int x;
semaphore S=1; //访问 X 的互斥信号量
process_P1{
int y,z; int t,u;
P(S); P(S);
x=1; x=0;
y=0; t=0;
if (x&amp;gt;=1) if (x&amp;lt;=1)
y=y + 1; t=t + 2;
V(S); V(S);
z=y; u=t;
}
## 02. 【解答】 使用信号量 mutex 控制两个进程互斥访问临界资源(仓库),使用同步信号量 Sa 和 Sb(分别代表产品 A 与 B 的还可容纳的数量差,以及产品 B 与 A 的还可容纳的数量差)满足条件 2 和条件 3。代码如下:
Semaphore Sa=M-1,Sb=N-1;
Semaphore mutex=1; //访问仓库的互斥信号量
process_A(){ process_B(){
while(1){ while(1){
P(Sa); P(Sb);
P(mutex); P(mutex);
A 产品入库; B 产品入库;
V(mutex); V(mutex);
V(Sb); V(Sa);
}
}
## 03. 【解答】 顾客进店后按序取号,并等待叫号;销售人员空闲后也按序叫号,并销售面包。因此同步算法只要对顾客取号和销售人员叫号进行合理同步即可。我们使用两个变量 i 和 j 分别记录当前的取号值和叫号值,并各自使用一个互斥信号量用于对 i 和 j 进行访问和修改。
int i=0,j=0;
semaphore mutex_i=1,mutex_j=1;
Consumer(){ //顾客
进入面包店;
P(mutex_i); //互斥访问 i
取号 i;
i++;
V(mutex_i); //释放对 i 的访问
等待叫号 i 并购买面包;
}
Seller(){ //销售人员
while(1){ //互斥访问 j
P(mutex_j); //可 j 已有顾客取走并等待
正于 j++;
V(mutex_j); //释放对 j 的访问
销售面包;
}
else{ //暂时没有顾客在等待
V(mutex_j); //释放对 j 的访问
<!-- page: 145 -->
休息片刻;
}
## 04. 【解答】 本题是生产者-消费者问题的变体,生产者“车间A”和消费者“装配车间”共享缓冲区“货架F1”;生产者“车间B”和消费者“装配车间”共享缓冲区“货架F2”。因此,可为它们设置6个信号量:empty1对应货架F1上的空闲空间,初值为10;full1对应货架F1上面的A产品,初值为0;empty2对应货架F2上的空闲空间,初值为10;full2对应货架F2上面的B产品,初值为0;mutex1用于互斥地访问货架F1,初值为1;mutex2用于互斥地访问货架F2,初值为1。 A 车间的工作过程可描述为:
while(1) {
生产一个产品A;
P(empty1); //判断货架 F1 是否有空
P(mutex1); //互斥访问货架 F1
将产品A存放到货架 F1 上;
V(mutex1); //释放货架 F1
V(full1); //货架 F1 上的零件 A 的个数加 1
}
B 车间的工作过程可描述为:
while(1) {
生产一个产品B;
P(empty2); //判断货架 F2 是否有空
P(mutex2); //互斥访问货架 F2
将产品B存放到货架 F2 上;
V(mutex2); //释放货架 F2
V(full2); //货架 F2 上的零件 B 的个数加 1
}
装配车间的工作过程可描述为:
while(1) {
P(full1); //判断货架 F1 上是否有产品 A
P(mutex1); //互斥访问货架 F1
从货架 F1 上取一个 A 产品;
V(mutex1); //释放货架 F1
V(empty1); //货架 F1 上的空闲空间数加 1
P(full2); //判断货架 F2 上是否有产品 B
P(mutex2); //互斥访问货架 F2
从货架 F2 上取一个 B 产品;
V(mutex2); //释放货架 F2
V(empty2); //货架 F2 上的空闲空间数加 1
将取得的 A 产品和 B 产品组装成产品;
}
## 05. 【解答】 从井中取水并放入水缸是一个连续的动作,可视为一个进程;从缸中取水可视为另一个进程。设水井和水缸为临界资源,引入 well 和 vat:三个水桶无论是从井中取水还是将水倒入水缸都是一次一个,应该给它们一个信号量 pail,抢不到水桶的进程只好等待。水缸满时,不可以再放水,设置 empty 信号量来控制入水量;水缸空时,不可以取水,设置 full 信号量来控制。本题需要设置 5 个信号量来进行控制:
semaphore well=1; //用于互斥地访问水井
semaphore vat=1; //用于互斥地访问水缸


&lt;!-- page: 146 --&gt;

```txt
semaphore empty=10; //用于表示水缸中剩余空间能容纳的水的桶数
semaphore full=0; //表示水缸中的水的桶数
semaphore pail=3; //表示有多少个水桶可以用,初值为3
关注公众号【考研小舟】 免费考研资料&amp;无水印PDF
//小和尚
```txt
while(1) {
P(empty);
P(pail);
P(well);
从井中打一桶水;
V(well);
P(vat);
将水倒入水缸中;
V(vat);
V(full);
V(pail);
}
## 06.【解答】 为了控制三个进程依次使用输入设备进行输入,需分别设置三个信号量 S1, S2, S3,其中 S1 的初值为 1,S2 和 S3 的初值为 0。使用上述信号量后,三个进程不会同时使用输入设备,因此不必再为输入设备设置互斥信号量。另外,还需要设置信号量 Sb, Sy, Sz 来表示数据 b 是否已经输入,以及 y, z 是否已计算完成,它们的初值均为 0。三个进程的动作可描述为:
P1() {
P(S1);
从输入设备输入数据 a;
V(S2);
P(Sb);
x = a + b;
P(Sy);
P(Sz);
使用打印机打印出 x, y, z 的结果;
}
P2() {
P(S2);
从输入设备输入数据 b;
V(S3);
V(Sb);
y = a * b;
V(Sy);
V(Sy);
}
P3() {
P(S3);
从输入设备输入数据 c;
P(Sy);
Z = y + c - a;
}


&lt;!-- page: 147 --&gt;

```txt
V(Sz);
}
## 07.【解答】 1)桥上每次只能有一辆车行驶,所以只要设置一个信号量 bridge 就可判断桥是否使用,若在使用中,等待;若无人使用,则执行 P 操作进入;出桥后,执行 V 操作。
semaphore bridge=1; //用于互斥地访问桥
NtoS(){ //从北向南
P(bridge);
通过桥;
V(bridge);
}
StoN(){ //从南向北
P(bridge);
通过桥;
V(bridge);
2)桥上可以同方向多车行驶,需要设置 bridge,还需要对同方向车辆计数。为了防止同方向计数中同时申请 bridge 造成同方向不可同时行车的问题,要对计数过程加以保护,因此设置信号量 mutexSN 和 mutexNS。
int countSN=0; //用于表示从南到北的汽车数量
int countNS=0; //用于表示从北到南的汽车数量
semaphore mutexSN=1; //用于保护 countSN
semaphore mutexNS=1; //用于保护 countNS
semaphore bridge=1; //用于互斥地访问桥
StoN(){ //从南向北
P(mutexSN);
if(countSN==0)
P(bridge);
countSN++;
V(mutexSN);
过桥;
P(mutexSN);
countSN--;
if(countSN==0)
V(bridge);
V(mutexSN);
}
NtoS(){ //从北向南
P(mutexNS);
if(countNS==0)
P(bridge);
countNS++;
V(mutexNS);
过桥;
P(mutexNS);
countNS--;
if(countNS==0)
V(bridge);
V(mutexNS);
}
## 08.【解答】 1)这种机制不能实现资源的互斥访问。考虑如下情形: ① 初始化时,flag 数组的两个元素值均为 FALSE。 ② 线程 0 先执行,执行 while 循环语句时,因为 flag[1] 为 FALSE,所以顺利结束,不会被 <!-- page: 148 --> 卡住。假设这时来了一个时钟中断,打断了它的运行。 ③ 线程 1 去执行,执行 while 循环语句时,因为 flag[0] 为 FALSE,所以顺利结束,不会被卡住,然后进入临界区。 ④ 后来当线程 0 再执行时,也进入临界区,这样就同时有两个线程在临界区。 总结:不能成功的根本原因是无法保证 Enter_Critical_Section() 函数执行的原子性。我们从上面的软件实现方法中可以看出,对于两个进程间的互斥,最主要的问题是标志的检查和修改不能作为一个整体来执行,因此容易导致无法保证互斥访问的问题。 2)可能出现死锁。考虑如下情形: ① 初始化时,flag 数组的两个元素值均为 FALSE。 ② 线程 0 先执行,flag[0] 为 TRUE,假设这时来了一个时钟中断,打断了它的运行。 ③ 线程 1 去执行,flag[1] 为 TRUE,在执行 while 循环语句时,因为 flag[0] = TRUE,所以在这个地方被卡住,直到时间片用完。 ④ 线程 0 再执行时,因为 flag[1] 为 TRUE,它也在 while 循环语句的地方被卡住,所以这两个线程都无法执行下去,从而死锁。 09. 【解答】 用信号量与 PV 操作实现三名工人的合作。 首先不考虑死锁问题,工人 1 与工人 3、工人 2 与工人 3 构成生产者与消费者关系,这两对生产/消费关系通过共同的缓冲区相联系。从资源的角度来看,箱子中的空位置相当于工人 1 和工人 2 的资源,而车架和车轮相当于工人 3 的资源。 分析上述解法易见,当工人 1 推进速度较快时,箱中空位置可能完全被车架占满或只留有一个存放车轮的位置,此时工人 3 同时取 2 个车轮将无法得到,而工人 2 又无法将新加工的车轮放入箱中;当工人 2 推进速度较快时,箱中空位置可能完全被车轮占满,而此时工人 3 取车架将无法得到,而工人 1 又无法将新加工的车架放入箱中。上述两种情况都意味着死锁。为防止死锁的发生,箱中车架的数量不可超过 $N - 2$ ,车轮的数量不可超过 $N - 1$ ,这些限制可以用两个信号量来表达。具体解答如下。
semaphore empty = N;     //空位置
semaphore wheel = 0;   //车轮
semaphore frame = 0;   //车架
semaphore s1 = N - 2;   //车架最大数
semaphore s2 = N - 1;   //车轮最大数
工人 1 活动:
do {
加工一个车架:
P(s1);                    //检查车架数是否达到最大值
P(empty);            //检查是否有空位
车架放入箱中:
V(frame);            //车架数加 1
} while (1):
工人 2 活动:
do {
加工一个车轮:
P(s2);                    //检查车轮数是否达到最大值
P(empty);            //检查是否有空位
车轮放入箱中:
V(wheel);              //车轮数加 1
} while (1):
}


&lt;!-- page: 149 --&gt;

工人3活动:

```c
do{
P(frame); //检查是否有车架
箱中取一车架;
V(empty); //空位数加1
V(s1); //可装入车架数加1
P(wheel); //检查是否有一个车轮
P(wheel); //检查是否有另一个车轮
箱中取二车轮;
V(empty); //取走一个车轮,空位数加1
V(empty); //取走另一个车轮,空位数加1
V(s2); //可装入车轮数加1
V(s2); //可装入车轮数再加1
组装为一台车;
}while(1);
## 10.【解答】 P, Q 构成消费者-生产者关系,因此设三个信号量 full, empty, mutex。full 和 empty 用来控制缓冲池状态,mutex 用来互斥进入。R 既为消费者又为生产者,因此必须在执行前判断状态,若 empty==1,则执行生产者功能;若 full==1,执行消费者功能。
semaphore full=0; //表示缓冲区的产品
semaphore empty=1; //表示缓冲区的空位
semaphore mutex=1; //互斥信号量
Procedure P    Procedure Q    Procedure R
{                while(TRUE){        {                if(empty==1){                    p sculpts;                    p empty);                    p empty);
P(mutex);            P(mutex);            P(mutex);
Product one;        consume one;        product one;
v(mutex);            v(mutex);            v(mutex);
v(full);              v(empty);            v(full);
}
}
## 11.【解答】 1)控制变量 waiting 用来记录等候理发的顾客数,初值为 0,进来一名顾客时,waiting 加 1,一名顾客理发时,waiting 减 1。 2)信号量 customers 用来记录等候理发的顾客数,并用作阻塞理发师进程,初值为 0。 3)信号量 barbers 用来记录正在等候顾客的理发师数,并用作阻塞顾客进程,初值为 0。 4)信号量 mutex 用于互斥,初值为 1。
int waiting=0; //等候理发的顾客数
int chairs=n; //为顾客准备的椅子数
semaphore customers=0,barbers=0,mutex=1;
barber(){ //理发师进程
while(1){ //理完一人,还有顾客吗?
P(customers); //若无顾客,理发师睡眠
P(mutex); //进程互斥


&lt;!-- page: 150 --&gt;

```txt
waiting=waiting-1; //等候顾客数少一个
V(barbers); //理发师去为一名顾客理发
V(mutex); //开放临界区
Cut_hair(); //正在理发
}
}
customer() { //顾客进程
P(mutex); //进程互斥
if (waiting&amp;lt;chairs) { //若有空的椅子,就找到椅子坐下等待
waiting=waiting+1; //等候顾客数加1
V(customers); //呼唤理发师
V(mutex); //开放临界区
P(barbers); //无理发师,顾客坐着
get_haircut(); //一名顾客坐下等待理发
}
else
V(mutex); //人满,离开
}
## 12.【解答】 电影院一次只能放映一部影片,希望观看的观众可能有不同的爱好,但每次只能满足部分观众的需求,即希望观看另外两部影片的用户只能等待。分别为三部影片设置三个信号量 $s_0, s_1, s_2$,初值分别为 $1, 1, 1$。电影院一次只能放一部影片,因此需要互斥使用。观看影片的观众有多个,因此必须分别设置三个计数器(初值都是0),用来统计观众个数。当然,计数器是个共享变量,需要互斥使用。
semaphore s=1,s0=1,s1=1,s2=1;
int count0=0,count1=0,count2=0;

process videoshow1 { //看第一部影片的观众
P(s0);
count0=count0+1;
if (count0==1)
P(s);
V(s0);
看影片;
P(s0);
count0=count0-1;
if (count0==0) //没人看了,就结束放映
V(s);
V(s0);
}

process videoshow2 { //看第二部影片的观众
P(s1);
count1=count1+1;
if (count1==1)
P(s);
V(s1);
看影片;
P(s1);
count1=count1-1;
if (count1==0) //没人看了,就结束放映
V(s);
V(s1);
}

process videoshow3 { //看第三部影片的观众
P(s2);
count2=count2+1;


&lt;!-- page: 151 --&gt;

```c
if(count2==1)
P(s);
V(s2);
看影片;
P(s2);
Count2=count2-1;
if(count2==0)
V(s);
V(s2);
## 13.【解答】 在汽车行驶过程中,驾驶员活动与售票员活动之间的同步关系为:售票员关车门后,向驾驶员发开车信号,驾驶员接到开车信号后启动车辆,在汽车正常行驶过程中售票员售票,到站时驾驶员停车,售票员在车停后开门让乘客上下车。因此,驾驶员启动车辆的动作必须与售票员关车门的动作同步;售票员开车门的动作也必须与驾驶员停车同步。应设置两个信号量S1,S2:S1表示是否允许驾驶员启动汽车(初值为0);S2表示是否允许售票员开门(初值为0)。
semaphore S1=0,S2=0;
Procedure driver
{
while(1)
{
P(S1);
Start;
Driving;
Stop;
V(S2);
}
}
```c Procedure Conductor { while(1) { 关车门; V(s1); 售票; P(s2); 开车门; 上下乘客; } ```