PV操作练习题

【例题1】

【题目】在一个仓库中可以存放A和B两个产品,要求:
(1)每次只能存入一个产品;
(2)A产品数量 - B产品数量 < M
  (3) B产品数量 - A产品数量 < N
其中,M、N是正整数,试用P操作、V操作描述产品A与产品B的入库过程。
注意,条件(2)和条件(3)中指明的A产品数量和B产品数量均指的是仓库里现有的数量。

思路:

  • 同步关系的处理。
    分析进程的行为,可能会阻塞等待的地方就P一下,再分析可以在哪里将其唤醒(V以下),再确定应该对应什么信号量。
  • 分析互斥关系:仓库中每次只能存入一种产品,因此仓库是一个临界资源,需要保证在存入产品时要互斥地进行。
  • 分析同步关系:A产品不能无限放,B产品也不能无限放,那么什么情况下能够放入A产品和B产品呢?最多能放入多少个A产品和B产品呢?由条件(2)知道,仓库中最少的A产品或B产品数量为0个,因此A产品最多可以放入仓库M-1个,B产品最多可以放入仓库N-1个。
    1.第一对同步关系:A产品此时放到了最大数量,此时如果还要继续放入A产品的话必须要等到B产品放入之后。因此设置同步信号量Sa,表示还可以放入仓库中A产品的数量。初始时由于仓库为空,最多可以放入A产品M-1个。当Sa=0时,表示仓库中已经不能放入A产品了,进程A每次放入A产品时都会对Sa执行P操作,表示可放入仓库中A产品的数量减少1个。在P操作之前如果Sa = 0,说明已经不能往仓库里面放入A产品了,此时P操作执行之后Sa会等于-1,A进程被阻塞,那么就只能根据条件(2)来增加B产品的数量从而使A产品可以继续放,因此若A产品不能再放了,只有等到B产品放入仓库之后,才能唤醒被阻塞的A进程。
    2.第二对同步关系:和第一对类似,若B放到了最大数量,只有等到放入了A产品,才能继续放入B产品。因此设置同步信号量Sb,初始值为N - 1.
    关键:每次在放入产品之前都要对同步信号量执行P操作,表示该产品可放入的数量减少1,同时要保证临界区的互斥访问。放入仓库之后需要唤醒另外一个产品可放置的数量。

【解析】

Semaphore mutex = 1;//实现互斥地访问仓库
Semaphore Sa = M - 1;//还能放入几个A产品
Semaphore Sb = N - 1;//还能放入几个B产品

//进程A,不断循环放入A产品
process_A() {
	while(1) {
		生产A产品;
		P(Sa);
		P(mutex);
		A产品入库;
		V(mutex);
		V(Sb);
	}
}
//进程B,不断循环放入B产品
process_B() {
	while(1) {
		生产B产品;
		P(Sb);
		P(mutex);
		B产品入库;
		V(mutex);
		V(Sb);
	}
}

【例题2】

【题目】面包师有很多面包,由n个销售人员推销。每个顾客进店后取一个号,并且等待叫号,当一个销售人员空闲下来时,就叫下一个号.试设计一个使销售人员和顾客同步的算法。

【方法一】

  • 需要保证每个顾客取号码是互斥进行的,不然有可能会出现取到同一个号的情况。(比如,一个顾客进程取一个号码i,此时另外一个顾客进程也在取号,若前者取到号码后号码还来不及递增,后者就会和前者取到相同的号,因此需要保证取号和取号后的号码递增是一气呵成的,其中一个顾客取号时其他顾客均不能取号)同理,每个销售人员叫号也是互斥进行的。因此需要设置互斥信号量。
  • 取到号之后号码要递增,叫号之后号码也需要递增,因此需要设置两个递增的变量。
  • 顾客进程是每一个人只取一次号,销售人员进程是循环等待叫号(只要销售人员空闲,就可以叫下一个号),因此需要注意销售人员进程的代码中要有循环。
  • 由于顾客取号和销售人员叫号都是一气呵成的,因此最后一个顾客叫号之后,号码仍然需要递增,此时的这个取号的变量i的值要比顾客手中号码最大值还要大1.销售人员每次取号时,应该满足取到的号码j要比这个i要小,如果大于等于i,销售人员需要循环休息,知道顾客取号之后i的值又增大了导致j < i才可以继续叫号。
  • 若有多个销售人员进程同时执行,需要保证每个销售人员叫号是互斥的。那么,什么时候释放对叫号变量的锁呢?第一种情况是只要叫号且叫号变量递增之后释放,此时表示当前销售人员进程已经叫完了,其他销售人员可以继续叫号。第二种情况是没有顾客等待了,此时叫号也没用,需要立即释放锁。
int i = 0;//顾客叫到哪个号了
int j = 0;//销售员叫到哪个号了
semaphore mutex_i = 1;//互斥访问i
semaphore mutex_j = 1;//互斥访问j

Consumer() { //顾客
	进入面包店;
	P(mutex_i);//互斥访问i
	取号i;
	i++;
	V(mutex_i);//释放对i的访问
	等待叫号i并购买面包;

}

Seller() //销售人员
{
	while(1) {
		P(mutex_j);//互斥访问j
		if(j < i) { //还有顾客在等待
		{
			叫号j;
			j++;
			V(mutex_j);//释放对j的访问
			销售面包;
		}  
		else //暂时没有顾客在等待
		{
			V(mutex_j);//释放对j的访问
			休息片刻;
		}
	}
}

【方法二】
分析顾客和销售员的动作,判断哪个动作先,哪个动作后,在先进行的动作后面执行V操作,后进行的动作前执行P操作。
1.顾客只有取了号,销售员才能叫号。可设置同步信号量Cs,表示有几个已经取号但没有得到服务的顾客(因为顾客得到服务的话就是表明销售人员已经叫过号了,Cs的值就需要相应减少),初始时值为0,表示店里没有顾客取号,之后每个顾客取一次号,Cs就加1,需要等待被叫号(被服务)一次才能相应减少一个。
2.销售员只有叫了号,顾客才能被服务。设置同步信号量Sm。顾客等待被叫号后需要阻塞,直到销售员叫号后才能被唤醒,顾客才能得到服务,销售员每唤醒一次,就有一个顾客得到服务。

Semaphore Sm = 0;//实现同步关系,销售员叫号后唤醒正在等待的顾客
Semaphore Cs = 0;//有几个已经取号但未得到服务的顾客
//第i个顾客,范围为[1,正无穷]
consumer_i() {
    进店;
    
    取号;
    V(Cs);
    等待被叫号;
    P(Sm);
    被服务;
}

//第j个销售员,范围为[1,正无穷]
salesman_j() {
    while(1) {
        P(Cs);
        叫号;
        V(Sm);
        为顾客服务;
    }
}

【例题3】

【题目】某工厂有两个生产车间和一个装配车间,两个生产车间分别生产A和B两种零件,装配车间的任务是把A和B两种零件组装成产品。两个生产车间每生产一个零件后,都要分别把它们送到装配车间的货架F1、F2上。F1存放零件A,F2存放零件B,F1和F2的容量均可存放10个零件。装配工人每次从货架上取出一个零件A和一个零件B后组装成产品。请用P、V操作进行正确管理。
【分析】

货架F1、货架F2相当于容量为10的缓冲区,对缓冲区的访问应遵循互斥原则。

1.互斥关系

  • A车间往货架F1上放零件A。装配车间从货架F1上取零件A。需要保证对货架F1的互斥进行。设置互斥信号量mutex1.
  • B车间往货架F2上放零件B。装配车间从货架F2上取零件B。需要保证对货架F2的互斥进行。设置互斥信号量mutex2.

2.同步关系

  • 只有货架F1有空位时,A车间才能放入零件A,设置同步信号量empty1 = 10,表示货架F1上有几个空位。
  • 只有货架F1不为空时,装配车间才能取走一个零件A,设置同步信号量,full1 = 0,表示货架F1上有几个A零件。
  • 只有货架F2有空位时,B车间才能放入零件B,设置同步信号量empty2 = 10,表示货架F2上有几个空位。
  • 只有货架F2不为空时,装配车间才能取走一个零件B,设置同步信号量,full2 = 0,表示货架F2上有几个B零件。

在这里插入图片描述
本题是生产者-消费者问题的变体。
生产者车间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.
【代码示例】

Semaphore mutex1 = 1;//互斥访问货架F1
Semaphore mutex2 = 1;//互斥访问货架F2
Semaphore empty1 = 10;//货架F1上有几个空位
Semaphore full1 = 0;//货架F1上有几个A零件
Semaphore empty2 = 10;//货架F2上有几个空位
Semaphore full2 = 0;//货架F2上有几个B零件

//A车间的工作过程可描述为:
process_A() {
    while(1) {
        生产一个产品A;
        P(empty1);
        P(mutex1);
        将产品A存放到货架F1上;
        V(mutex1);
        V(full1);
    }
}

//B车间的工作过程可描述为:
process_B() {
    while(1) {
        生产一个产品B;
        P(empty2);
        P(mutex2);
        将产品B存放到货架F2上;
        V(mutex2);
        V(full2);
    }
}

//装配车间的工作过可描述为:
assembly_shop() {
    while(1) {
        P(full1);
        P(mutex1);
        从货架F1上取一个A产品;
        V(mutex1);
        V(empty1);
        P(full2);
        P(mutex2);
        从货架F2上取一个B产品;
        V(mutex2);
        V(empty2);
        将取得的A产品和B产品组装成产品;
    }
}

【例题4】

在这里插入图片描述
【分析】

  • 小和尚从井里取水时需要一个水桶,老和尚从水缸里取水时需要一个水桶,老和尚要从井里取水的话每次只能由一个老和尚去取。(每次入缸取水仅为一桶水,且不可同时进行)
  • 水缸和水井都是一个互斥的缓冲区
  • 水缸可以看成是一个生产者-消费者模型中的缓冲区。初始容量为10,表示水缸中剩余空间能容纳的水桶数。小和尚相当于生产者,在从水井里面取水之前就应该先对empty执行一下P操作,确保可以在放入水缸一桶水。(如果从井里取出水后才P操作,水缸又正好满了,那么水岂不是白打了!)。如果水缸已经满了去打水,有可能导致死锁。老和尚从水缸里打水之后,就导致水缸的容量加1。对empty执行V操作。老和尚相当于消费者,在向水缸里取水之前需要P(full)一下确定水缸里面有水,小和尚往水缸里面倒入水之后水缸里面水桶数+1,即V(full)
  • 小和尚打水和老和尚打水都需要用一个桶,用完后归还。3个桶就是3个互斥使用的资源,设置信号量pail,初值为3,表示有3个水桶可以用。
    在这里插入图片描述
    从井里取水并放入水缸是一个连续的动作,可视为一个进程;从缸中取水可视为另一个进程。设水井和水缸为临界资源,引入well和vat;三个水桶无论是从井中取水还是将水倒入水缸都是一次一个,应该给它们一个信号量pail,抢不到水桶的进程只好等待。水缸满时,不可以在放水,设置empty信号量来控制入水量。水缸空时,不可以取水,设置full信号量来控制。本题设置5个信号量来控制。
    【代码示例】
Semaphore well = 1;//用于互斥地访问水井;
Semaphore vat = 1;//用于互斥地访问水缸
Semaphore empty = 10;//用于表示水缸中剩余空间能容纳的水桶数
Semaphore full = 0;//表示水缸中水的桶数
Semaphore pail = 3;//表示有多少个水桶可以用,初值为3

//老和尚进程

while(1) {
    P(full);//先确保水缸有水
    P(pail);//拿一个水桶
    P(vat);//老和尚之间互斥访问水缸,每次入缸取水仅为一桶水,且不可同时进行
    从水缸中打一桶水;
    V(vat);
    V(empty);//消费者进程取走产品,空位加1
    喝水;
    V(pail);
}

//小和尚进程
while(1) {
    P(empty);//确保可以往水缸里面放水
    P(pail);
    P(well);//小和尚之间互斥地访问水桶,每次只能容一个桶取水。
    从井中打一桶水;
    V(well);
    P(vat);//生产者与消费者之间对水缸的访问需要互斥进行
    将水倒入水缸中;
    V(vat);
    V(full);//水缸的水桶数+1
    V(pail);
}

规律:生产者先P(empty)后V(full),消费者先P(full)再V(empty).
注意:下列情况有可能发生死锁:
在这里插入图片描述
上图操作对于老和尚就是先取桶,再确定水缸里面有没有水,小和尚也是先取桶,再确定水缸里面可不可以放水,比如有3个小和尚,现在水缸已经满了,他们各自取走一个桶,即有3个小和尚进程执行P(pail),他们在P(empty)的时候发现水缸已经满了。3个小和尚都会被阻塞在第二个P操作P(empty)这里,都无法推进下去,并且此时他们每个人手里都持有一个桶资源。此时老和尚想要喝水,先P(pail),发现桶没了,老和尚也阻塞,就算水缸是满的,老和尚由于没有得到水桶而喝不了水。所以必须确定缓冲区里面有容量或者缓冲区里面有空位才能取桶。而不是先霸占资源,再去确定缓冲区的问题。

【例题5】

【题目】
在这里插入图片描述
在这里插入图片描述
【解题步骤】
1.分析各个进程的执行步骤,构建代码骨架。

P1() {
	
	从输入设备输入数据a;
	x = a + b;
	使用打印机打印x、y、z;
	
}

P2() {
	从输入设备输入数据b
	y = a * b;
}

P3() {
	从输入数据输入数据C;
	z = y + c - a;
}

2.分析前驱关系。
P1读a->P2读b:信号量S2,初值为0
P2读b->P3读c: 信号量S3,初值为0

x = a + b:
首先P1必须读a,P2必须读b
P1读a->P1算x:由代码逻辑确定,不需要设置信号量。
P2读b->P1算x:不同进程之间协作,需要设置同步信号量Sb.

y = a * b:
首先P1必须读a,P2必须读b
P1读a->P2算y:只要运行到y = a * b这句代码,说明数据b已经读入了,那么数据a肯定已经读入了(因为我们已经用同步信号量S2保证了这个事情)
P2读b->P2算y:由代码逻辑确定,不需要设置信号量。
所以再执行y = a* b之前,不需要进行P操作。

z = y + c - a;
首先必须P1读a,P3读c,P2算y
执行到z=y+c-a说明已经执行过“从输入设备输入数据c”了,只要数据c读入了,数据a肯定也已经读入了。这是由代码逻辑确定的。唯一需要设置同步信号量的地方就是
P2算y->P3算z:发生在两个不同的进程之间,需要使用同步信号量维持前驱关系。设置同步信号量Sy

计算完x,y,z之后,P1就可以将x,y,z用打印机输出了,说明x,y,z这三个值已经准备好了。x是由P1进程的代码逻辑确定的,而y的值和z的值分别由P2和P3进程产生。发生在不同进程之间,需要设置同步信号量
P2算y->P1输出x,y,z:需要设置同步信号量Sy
(可以在计算y之后进行两次V(Sy),从而也可以进行两次P(Sy)),也可以重新设置信号量。

P3算z->P1输出x,y,z:

题目说打印机设备必须互斥地使用,那么是不是意味着我们需要设置一个互斥信号量mutex=1,然后在各个进程使用输入设备之前分别P和V一下,其实不用,因为S2,S3这两对同步关系就已经保证了同一时刻只可能有一个进程在使用这一个输出设备。但是如果加了互斥信号量也没有问题。

3.画出前驱图
在这里插入图片描述

【代码示例】

Semaphore S1 = 1;//S1这个有没有无所谓
Semaphore S2 = 0;
Semaphore S3 = 0;
Semaphore S4 = 0;
Semaphore Sy = 0;
Semaphore Sz = 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;
    V(Sz);

}

【例题6】

有一座桥如下图所示。车流方向如箭头所示。回答如下问题:
1)假设该桥上每次只能有一辆车行驶,试用信号灯的PV操作实现交通管理。
2) 假设该桥上不允许两车交会,但允许同方向多个车一次通过(即桥上可有多个同方向行驶的车)。试用信号灯的PV操作实现桥上交通管理。

在这里插入图片描述
【解答】
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);
}

【例题7】

在这里插入图片描述
在这里插入图片描述

如果箱子里面放满了车架,导致工人3取不到车轮而阻塞,而工人1和工人2也放不了车架和车轮到箱子里面,工人1和工人2也会阻塞。
那么车架最多可以放多少个才能避免发生死锁呢?
如果我们限制箱子里最多只能放N-2个车架,也就是说至少有两个空位是专门用来放车轮的。工人2至少能放两个车轮,工人3取车轮的动作也可以往下进行了。
同样地,如果箱子里全部放了车轮,工人3执行第一句“箱中取一个车架”而箱子中没有车架,因此工人3阻塞,此时箱子已经满了,工人1和工人2也发生阻塞。
如果我们限制箱子里最多只能放N-1个车轮,也就是说至少有一个位置是专门用来放车架的,这样就肯定不会发生死锁。
首先所有工人都需要访问箱子,箱子可以看成是缓冲区,需要设置mutex变量实现各个进程对箱子的互斥访问。
工人1什么时候需要阻塞等待呢?第一种情况是箱子里面已经没有空位了,可以设置一个empty信号量,初始值为N,表示箱子初始时的空位为N,第二种情况就是车架的数量已经达到分析过的上限了(保证不发生死锁),由于P操作具有阻塞进程的功能,因此工人1将车架放入箱中之前需进行这2个P操作,那么什么时候唤醒呢?当工人3从箱子里面取走一个车架时就可以唤醒
对于工人3,需要取车架和取车轮,如果箱子里没有这些东西的话,就会被阻塞,因此在工人3取车架或车轮之前,需要对frame和wheel进行P操作。
用信号量与PV操作实现3名工人的合作。
首先不考虑死锁问题,工人1与工人3、工人2与工人3构成生产者-消费者关系,这两对生产消费关系通过共同的缓冲区相联系。从资源的角度看,箱子中的空位相当于工人1和工人2的资源,而车架和车轮相当于工人3的资源。
分析上述解法易见,当工人1推进速度较快时,箱中空位置可能完全被车架占满或支流有一个存放车轮的位置。此时工人3同时取两个车轮将无法的到,而工人2有无法将新加工的车轮放入箱中;当工人2推进速度较快时,箱中空位置可能完全被车轮占满,而此时工人3取车架将无法得到,而工人1又无法将新加工的车架放入箱中。上述的两种情况都意味着死锁,为防止死锁的发生,相中车架的数量不超超过N-2,车轮的数量不可超过N-1,这些限制可以用两个信号量来表达。具体解答如下:

Semaphore empty = N;//箱子中还有几个空位 相当于生产者进程中的empty
Semaphore wheel = 0;//箱子中有几个车轮 相当于消费者1进程中的full1
Semaphore frame = 0;//箱子中有几个车架 相当于消费者2进程中的full2
Semaphore wheelMax =  N - 2;//最多还能放几个车轮 每次放车轮前需要检查这个信号量,防止发生死锁
Semaphore frameMax = N - 1;//最多还能放几个车架 每次放车架前需要检查这个信号量,防止发生死锁。
Semaphore mutex = 1;//互斥访问箱子 相当于缓冲区


工人1活动:
do 
{
    加工一个车架;
    P(frameMax);//车架数已达到上限?注意不能和P(empty)调换,否则会发生死锁
    P(empty);//检查是否有空位
    P(mutex);
    车架放入箱中;
    V(mutex);
    V(frame);//车架的数量加1
} while(1);

工人2活动:
do 
{
    加工一个车轮;
    P(wheelMax);//车轮数是否已经达到上限?注意不能和P(empty)调换,否则会发生死锁
    P(empty);//检查是否有空位
    P(mutex);
    车轮放入箱中;
    V(mutex);
    V(wheel);//车轮数目加1
} while(1);

工人3活动:
do
{
    P(frame);//检查是否有车架
    箱中取一车架;
    V(empty);//空位数加1
    V(frameMax);//可装入车架数加1
    
    P(wheel);//检查是否有一个车轮
    P(wheel);//检查是否有另一个车轮
    箱中取2个车轮;
    V(empty);//空位数加1
    V(empty);//空位数加1
    V(wheelMax);//可装入车轮数+1
    V(wheelMax);//可装入车轮数再加1
    
    组装为一台车;
} while(1);

【例题8】

在这里插入图片描述
【解答】P,Q构成生产者-消费者关系,因此设三个信号量full,empty,mutex。full和empty用来控制缓冲池状态,mutex用来互斥进入。R既为生产者又为消费者,因此必须在执行前判断状态,若empty== 1,则执行生产者功能;若full ==1,执行消费者功能

semaphore full = 0;//表示缓冲区的产品
semaphore empty = 1;//表示缓冲区的空位
semaphore mutex = 1;//互斥信号量

Procedure P 
{
    while(TRUE) 
    {
        P(empty);
        P(mutex);
        	Product one;
        V(mutex);
        V(full);
    }
}

Procedure_Q
{
    while(TRUE)
    {
        P(full);
        P(mutex);
        	consume one;
        V(mutex);
        V(empty);
    }
}

Procedure R
{
    if(empty == 1) 
    {
        P(empty);
        P(mutex);
        	Product One;
        V(mutex);
        V(full);
    }
    if(full == 1) 
    {
        P(full);
        P(mutex);
        	consume one;
        V(mutex);
        V(empty);
    }
}

【例题9】

在这里插入图片描述

方法一

【分析】

  • 理发师所等待的资源是坐在椅子上等待的顾客。 可以设置同步信号量customers,初始值为0,表示刚开始没有顾客。理发师在操作之前先P(customers)确保有客户等待才能执行后面的理发,如果没有顾客等待,说明customers = 0,那么理发师就会被阻塞在P(customers)这一句代码。那么什么时候将其唤醒呢?当等待的顾客数加1的时候,就对custumers执行V操作,如果有源源不断的顾客进程到来的话,那么来一个customers就加一个(除非椅子满了,顾客直接就离开了,此时等候理发的顾客数目没有增多)。假设一个初始状态,理发师先上处理机运行,那么如果店里面没有顾客,就会被阻塞在P(customers),直到来了第一个顾客,就会唤醒理发师进程。假设是顾客进程先上处理机运行,那么customers就大于0,之后理发师进程就不会因为P(customers)而阻塞,两者都可以顺利执行下去。

  • 如果理发师能够通过P(customers)顺利往下执行的话,说明至少有一个顾客等待他的服务的,之后就可以进行理发了。

  • 对于顾客,进行理发店时会检查是否有空的椅子,如果有空椅子,就等待;否则就离开。

    我们可以设置一个int型的变量waiting,用来表示有多少个顾客正在等待,刚开始有n把椅子,最多有n个顾客进行等待。

    顾客进程在运行时首先会检查正在等待的人是否小于n个,如果成立,顾客就会占用其中的一把空椅子,waiting++;否则顾客就直接离开了。注意多个顾客进程并发运行的时候都有可能访问waiting这个变量,类似于读者写者问题的count变量,对waiting变量的访问应该也保证互斥。waiting若成功增加1之后需要执行什么动作了?waiting增加1意味着等待的顾客加1,此时顾客进程需要等待理发师。

    那么什么时候waiting会减少呢?在理发师理发之前,就会有顾客从等待的椅子中起身。

  • 假设同步信号量tony,之后对顾客进程需要执行P(tony),什么时候唤醒呢?我们根据生活经验知道,理发师在进行理发之前肯定要叫一位顾客,从而唤醒一个顾客进程,我们只需要在理发师理发的动作之前添加V(tony)即可。接下来我们要确定这个信号量的初始值为多少。假设是顾客进程先上处理机运行,那么P(tony)之后顾客需要立马阻塞,只有理发师执行了V(tony)才能为其服务,既然第一个顾客进程都会被阻塞,那么tony这个同步信号量的值就要设置为0.如果大于0就意味着第一个顾客进程可以顺利往下运行,这显然是不符合常理的。

  • 注意理发师进程需要一直循环等待服务顾客,而每个顾客进程只需要被服务一次。

下面针对此题的代码不管是一位理发师还是多位理发师都是有效的。
【写法一】

int waiting = 0;//正在等候理发的顾客数量
semaphore mutex = 1;//互斥地访问waiting变量
semaphore customers = 0;//用于实现“理发师等待顾客”的同步关系
semaphore tony = 0;//用于实现“顾客等待理发师”的同步关系

//理发师进程
TonyTeacher() {
	while (1) {
		P(customers);//等待顾客到来,无顾客则睡眠
		P(mutex);//互斥的访问waiting变量
		waiting--;//等待顾客数-1
		V(mutex);//开放临界区
		V(tony);//唤醒一个正在等待的顾客
		为顾客理发;
	}
}

//顾客进程
customer() {
	P(mutex);//互斥访问waiting变量
	if (waiting < n) //是否还有空椅子
	{
		waiting++;//等候的顾客数加1
		V(mutex);//访问完waiting变量
		V(customers);//唤醒理发师
		P(tony);//等待tony老师
		接受tony老师的改造;
	}
	else
	{
		V(mutex);//无座,转身离开
	}
}

【写法二】

int waiting = 0;
semaphore mutex = 1;
semaphore customers = 0;
semaphore tony = 0;

TonyTeacher() {
	while (1) {
		P(customers);
		V(tony);
		为顾客理发;
	}
}

customer() {
	P(mutex);
	if (waiting < n) {
		waiting++;
		V(mutex);
		V(customers);
		P(tony);
		
		//如果能够成功进入,说明有顾客从等待的椅子中离开并接受理发师的理发了
		P(mutex);
		waiting--;
		V(mutex);
		接受tony的改造;
	}
	else
	{
		V(mutex);
	}
}

方法二

  • 控制变量waiting用来记录等候理发的顾客数,初值为0,进来一个顾客时,waiting加1,一个顾客理发时,waiting减1
  • 信号量customers用来记录等候理发的顾客数,并用作阻塞理发师进程,初值为0
  • 信号量barbers用来记录正在等候顾客的理发师数,并用作阻塞顾客进程,初值为0
  • 信号量mutex用于实现互斥,初值为1.
int waiting = 0;//等候理发的顾客数
int chairs = n;//为顾客准备的椅子数量
semaphore customers = 0;//用于实现“理发师等待顾客”的同步关系
semaphore barbers = 0;//用于实现“顾客等待理发师”的同步关系
semaphore mutex = 1;//用于对waiting变量的互斥访问

//理发师进程
barber() {
    while(1) //理完一人,还有顾客吗
    {
        P(customers);//若无顾客,理发师睡眠
        P(mutex);//进程互斥
        waiting = waiting - 1;//等候的顾客数少一个
        V(mutex);//开放临界区
        V(barbers);//理发师去为一个顾客理发,唤醒正在等待的顾客进程
        Cut_hair();//正在理发
    }
}

//顾客进程
customer() {
    P(mutex);//进程互斥
    if(waiting < chairs) //若有空的椅子,就找到椅子坐下等待
    {
        waiting = waiting + 1;//等候顾客数加1
        V(mutex);//开放临界区
        V(customers);//唤醒理发师
        P(barbers);//无理发师,顾客坐着
        get_haircut();//一个顾客坐下等待理发
    }
    else
    {
        V(mutex);//人满,离开
    }
}

【例题10】

在这里插入图片描述
【分析】读者-写者问题的变种。

  • 同一类进程可以同时访问一类资源,不同类进程需要互斥地访问录音厅这个资源。
  • 第一个读者负责上锁,最后一个读者负责解锁。
semaphore room = 1;//互斥信号量,用于不同进程之间互斥地访问录像厅,同类进程可以并发访问录像厅
semaphore mutex1 = 1;//对count1互斥访问
semaphore mutex2 = 1;//对count2互斥访问
semaphore mutex3 = 1;//对count3互斥访问
int count1 = 0;//同时观看影片1的观众人数
int count2 = 0;//同时观看影片2的观众人数
int count3 = 0;//同时观看影片3的观众人数

//影片1对应的观众进程
P1() {
    P(mutex1);
    count1++;
    if(count1 == 1) { //第一个上锁
        P(room);
    }
    V(mutex1);
    看影片1;
    P(mutex1);
    count1--;
    if(count1 == 0) //最后一个解锁
    {
        V(room);
    }
    V(mutex1);
}

//影片2对应的观众进程
P2() {
    P(mutex2);
    count2++;
    if(count2 == 1) {
        P(room);
    }
    V(mutex2);
    看影片2;
    P(mutex2);
    count2--;
    if(count2 == 0)
    {
        V(room);
    }
    V(mutex2);
}

//影片3对应的观众进程
P3() {
    P(mutex3);
    count3++;
    if(count3 == 1) 
    {
       P(room);
    }
    V(mutex3);
    看影片3;
    P(mutex3);
    count3--;
    if(count3 == 0) 
    {
        V(room);
    }
    V(mutex3);
}

【例题11】


在这里插入图片描述
【分析】

  • 每次只允许一辆自行车通过。说明互斥性。
  • 这条路本来是个互斥资源,不管那个方向,一次只准一辆车通过。每个方向上的车同一时刻只允许一辆通过。可以设置一个互斥信号量T2N,N2T,进入之前P一下,离开之后V一下。
  • 有了安全岛之后,允许左边进一辆车、右边进一辆车,方向不一致也是可以的,因为它们可以在中间安全岛出错车。相当于一个转盘(环岛)
  • 如果从右往左的车占用了环岛右边的道路的话,那么从左往右的车无法进入。只有从右往左的车进入了安全岛之后,才能释放右边的路的访问权限。从左往右也是一样。
    【解答】
    由于安全岛M仅允许两辆车停留,本应作为临界资源而设置信号量,但根据题意,任意时刻进入安全岛的车不会超过两辆(两个方向最多各为一辆),因此不需要为M设置信号量,而要在路口T和路口N都设置信号量,以控制来自两个方向的车对路口资源的争夺。这两个信号量的初值都是1。此外,由于从N到T的一段路只允许一辆车通过,所以还需要设置另外的信号量用于控制。由于M的存在,可为两端的小路分别设置一个互斥信号量。
int T2N = 1;//从T到N的互斥信号量
int N2T = 1;//从N到T的互斥信号量
int L = 1;//经过L路段的互斥信号量
int K = 1;//经过K路段的互斥信号量

Procedure Bike T2N {
    P(T2N);//保证T->N方向一次只允许一辆车通过,这是这对T->N的同类进程而言的。
    
    //对L路段上锁,这是针对不同类进程而言的,当T->N的车在进入此路段时,防止N->T的车进入此路段
    P(L);
    从T进入L;
    进入M;
    V(L);
    
    //对K路段上锁
    P(K);
    从K到N;
    V(K);
    
    V(T2N);
}

Procedure Bike N2T {
    P(N2T);
    
    P(K);
    从N进入K;
    进入M;
    V(K);
    
    P(L);
    从L到T;
    V(L);
    
    V(N2T);
}

【例题12】

在这里插入图片描述
【分析】

  • 售票员先要关车门,驾驶员才可以启动车辆正常行驶
  • 车只有到站停稳之后,才有可能开车门。
semaphore P1 =  0;//同步信号量,关车门后,才能启动车辆
semaphore P2 = 0;//同步信号量,到站停车后,才能开车门

//老司机
while(1) {
    P(S1);
    启动车辆;
    
    正常行车;
    
    到站停车;
    V(S2);
} 

//售票员
while(1) 
{
    
    关车门;
    V(S1);
    售票;
    P(S2);
    开车门;
    
}

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

semaphore S1  = 0;
semaphore S2 = 0;
Procedure driver
{
    while(1) 
    {
        P(S1);
        Start;
        Driving;
        Stop;
        V(S2);
        
    }
}

Procedure Conductor 
{
    while(1) 
    {
        关车门;
        V(S1);
        售票;
        P(S2);
        开车门;
        上下乘客;
    }
}

【例题12】

在这里插入图片描述
【分析】

  • 提取问题模型:一个生产者生产多种产品,多个消费者取走各自的产品—吸烟者问题。

  • 互斥关系:首先三个进程需要互斥地访问缓冲区。设置互斥信号量mutex。在各个进程对缓冲区操作之前都进行P操作,访问完之后进行V操作。

  • 同步关系:刚开始缓冲区是空的,P2、P3想取走自己的产品是取不到的。需要等到P1把自己的产品put进去之后才能进行。P1、P2因为奇数的放置和取用

    1. P1产生奇数之后P2才可以取走奇数。设置一个和奇数有关的同步信号量odd

    2. P1产生偶数之后P3才可以取走偶数。设置一个和偶数相关的同步信号量even

    3. P1、P2、P3由于共享缓冲区,设同步信号量empty,初值为N。当缓冲区已经放满时,P1不能再放,需要阻塞,需要等待P2或者P3取走一个产品之后次啊能被唤醒。设置一个同步信号量empty = N(看箭头的指向,指向生产者进程一般都是P(empty),指向消费者进程一般都是V(full).)
      在这里插入图片描述
      【注意】同步和互斥的P操作互换位置之后有可能导致死锁问题。如P(odd)和P(mutex)不能互换位置。

semaphore mutex = 1;//缓冲区操作互斥信号量
semaphore odd = 0;//有几个奇数
semaphore even = 0;//有几个偶数
semaphore empty = N;//空缓冲区的数量

cobegin 
{
    //进程P1:生产整数
    Process P1() 
    {
       while(1) 
       {
           x = produce();
           P(empty);
           P(mutex);
           put();
           V(mutex);
           if(x % 2 == 1)
           {
               V(odd);
           }
           else
           {
               V(even);
           }
       }
   
    }
    
    //进程P2:取出奇数
    Process P2() 
    {
       while(1) 
       {
           P(odd);
           P(mutex);
           getOdd();
           V(mutex);
           V(empty);
           countOdd();
       }
    }
    
    //进程P3:取出偶数
    Process P3() 
    {
      while(1) 
      {
          P(even);
          P(mutex);
          getEven();
          V(mutex);
          V(empty);
          countEven();
      }
    }   
}

【例题13】

在这里插入图片描述
【分析】

A.同步关系

  • 顾客到达银行之后,首先需要判断是否有空座位,有空座位的时候才可以去取号机上去取号。定义一个同步信号量:empty,初值为10.顾客等待的就是空座位这个资源。
  • 营业员空闲的时候,才可以叫号。设置一个同步信号量service来表示这对同步关系。为什么初始值设置为0呢?
  • 有顾客时服务员才可以叫号。

有空座位->取号

营业员空闲->叫号

有顾客->叫号

顾客的有无决定了营业员是否能开始服务

空座位的有无影响等待顾客的数量

B.互斥关系

  • 取号机每次只允许一个顾客使用,设置一个互斥信号量来互斥地访问取号机。互斥资源:取号机(一次只有一位顾客取号),因此设置同步信号量mutex
semaphore empty = 10;//空座位的数量,初值为10有空座位->取号的同步关系
semaphore mutex = 1;//互斥使用取号机 
semaphore service = 0;//等待叫号 营业员空闲->叫号的同步关系
semaphore full = 0;//已占座位的数量 有顾客->叫号的同步关系

cobegin
{
   Process 顾客i
   {
		
       P(empty);//等空位
       P(mutex);//申请使用取号机
       从取号机上取号;
  	   V(mutex);//取号完毕
   	   V(full);//通知营业员有新顾客
       等待叫号;
	   P(service);//等待营业员叫号
       接受服务;
       
   		
   }
    
    Process 营业员
    {
        while(True)
        {
      	    P(full);//没有顾客则休息
            V(empty);//离开座位
            叫号;
    	    V(service);//叫号
            为顾客服务;

        }
    }
} coend;

【例题14】

在这里插入图片描述
【分析】出入口一次仅允许一个人通过,设置互斥信号量mutex,初值为1.博物馆最多可同时容纳500人,因此设置信号量,初值为500

semaphore empty = 500;//博物馆可以容纳的最多人数
semaphore mutex = 1;//用于出入口资源的控制
cobegin
{
	参观者进程i() {
		...
		P(empty);//可容纳人数减1
		P(mutex);
		进门;
		V(mutex);
		参观;
		P(mutex);//互斥使用门
		出门;
		V(mutex);
		V(empty);//可容纳人数减1
		...
	}
} coend;

【例题15】

在这里插入图片描述
【分析】这是典型的生产者和消费者问题,只是对典型问题加了一条条件,只需在标准模型上新加一个信号量,即可完成指定的要求。
设置四个变量mutex、mutex2.empty和full
mutex2用于控制一个消费者进程在一个周期(10次)内对缓冲区的访问,初值为1;
mutex用于控制进程单词互斥地访问缓冲区,初值为1.
empty代表缓冲区的空位数,初值为1000.
full代表缓冲区的产品数,初值为0.
具体进程描述如下:

semaphore mutex = 1;//各进程互斥地访问缓冲区
semaphore empty = 1000;//有几个空闲缓冲单元
semaphore full = 0;//有几个产品
semaphore mutex2 = 1;//实现连取10次

//生产者进程
producer() {
	while(1) {
		生产一个产品;
		P(empty);//判断缓冲区是否有空位
		P(mutex);//互斥访问缓冲区
		把产品放入缓冲区;
		V(mutex);//互斥访问缓冲区
		V(full);//产品的数量加1
	}
}

//消费者进程
consumer() {
	while(1) {
		P(mutex2);//连续取10次
		for(int i  = 0; i < 10;i++) 
		{
			P(full);//判断缓冲区是否有产品
			P(mutex);//互斥访问缓冲区
			从缓冲区取出一件产品;
			V(mutex);//互斥访问缓冲区
			V(empty);//腾出一个空位
			消费这件产品;
		}
		V(mutex2);
	}
}

【拓展】:用代码实现循环缓冲区的逻辑

int buff[1000];//大小为1000的循环缓冲区
int front  = 0;//队头指针
int rear = 0;//队尾指针

semaphore mutex = 1;
semaphore empty = 1000;
semaphore full = 0;
semaphore mutex2 = 1;

//生产者进程
producer() 
{
	while(1) 
	{
		x = produce();//生产一个产品;
		P(empty);//判断缓冲区是否有空位
		P(mutex);//互斥访问缓冲区
		buff[rear] = x;//把产品放入缓冲区
		rear = (rear + 1% 1000;//实现循环
		v(mutex);//互斥访问缓冲区
		V(full);//产品的数量加1
	}
}

//消费者进程
consumer() 
{
	while(1) 
	{
		P(mutex2);//连续取10次
		for(int i = 0;i < 10;i++)
		{
			P(full);//判断缓冲区是否有产品
			P(mutex);//互斥访问缓冲区
			x = buff[front];//从缓冲区取走一件产品
			front = (front + 1) % 1000;//实现循环
			V(mutex);//互斥访问缓冲区
			V(empty);
			consume(x);//消费这件产品
		}
		V(mutex2);
	}
}

【例题16】

在这里插入图片描述
在这里插入图片描述
【分析】

1.互斥关系

  • 题目中的信箱就是一个缓冲区,对缓冲区的访问需要互斥进行。

2.同步关系

  • A的信箱不为空时,才能从A信箱中取走邮件。定义信号量Full_A.如果为空,消费者进程(A)就不能取走邮件。
  • B的信箱不为空时,才能从B信箱中取走邮件。定义信号量Full_B.如果为空,消费者进程(B)就不能取走邮件
  • 放入邮件进B的信箱之前,必须要检查缓冲区是否已满,如果满了,需要阻塞。设置同步信号量Empty_B.生产者进程A在放入邮件到B的信箱之前,需要执行P操作,需要在B取走邮件之后唤醒
  • 放入邮件进A的信箱之前,必须要检查缓冲区是否已满,如果满了,需要阻塞,设置同步信号量Empty_A.生产者进程B在放入邮件到A的信箱之前,需要执行P操作,需要在A取走邮件之后唤醒
semaphore mutex_A = 1;//互斥访问A的信箱
semaphore mutex_B = 1;//互斥访问B的信箱
semaphore Full_A = x;//A的信箱中的邮件数量
semaphore Full_B = y;//B的信箱中的邮件数量
semaphore Empty_B = N - y;//B的信箱中还可以存放几个邮件
semaphore Empty_A = M - x;//A的信箱中还可以存放几个邮件
Cobegin
{
    A() 
    {
        while(True)
        {
            P(Full_A);
            P(mutex_A);
            从A的信箱中取个邮件;
            V(mutex_A);
            V(Empty_A);
            回答问题并提个新问题;
		   
		   P(Empty_B);
            P(mutex_B);
            将新邮件放入B的信箱;
            P(mutex_B);
            V(Full_B);
        }
    }
    
    B() 
    {
       while(True)
       {
            P(Full_B);
            P(mutex_B);
            从B的信箱中取个邮件;
            V(mutex_B);
		   V(Empty_B);
            回答问题并提个新问题;

		   P(Empty_A);
            P(mutex_A);
            将新邮件放入A的信箱;
            V(mutex_A);
            V(Full_A);
           
       }
    }
    
} coend;

【例题17】

在这里插入图片描述
【分析】
注意,每个线程代码中的w是分别属于线程本身的局部变量
首先,画出每个线程对全局变量的访问情况。
在这里插入图片描述
发现y变量是T1、T2、T2共有的,需要对y变量实现互斥访问。
z变量是T2、T3共有的,需要对z变量实现互斥访问。
T3线程相当于对y、z又读又写。
而T1、T2这两个线程有可能同时读y,如果只是设置了mute_y这个信号量的话,就无法实现T1和T2线程同时读y。
读者和读者之间应该不互斥,但是写者和读者之间应该互斥。
首先T1和T3都会访问到y,T1是读者,T3是写者。设置mutex_y1
T2和T3都会访问到y,T2是读者,T3是写者。设置mutex_y2
T2和T3都会访问到z,T2是读者,T3是写者。设置mutex_z

semaphore mutex_y1 = 1;//mutex_y1用于thread1和thread2对变量y的互斥访问
semaphore mutex_y2 = 1;//mutex_y2用于thread2与thread3对变量y的互斥访问
semaphore mutex_z = 1;//mutex_z用于变量z的互斥访问

//T1线程
thread1() {
	cnum w;
	P(mutex_y1);
	w = add(y,z);
	V(mutex_y1);
}

//T2线程
thread2() {
	cnum w;
	P(mutex_y2);
	P(mutex_z);
	w = add(y,z);
	V(mutex_z);
	V(mutex_y2);
}

//T3线程
thread3() 
{
	cnum w;
	w.a = 1;
	w.b = 1;
	P(mutex_z);
	z = add(z,w);
	V(mutex_z);
	P(mutex_y2);
	P(mutex_y1);
	y = add(y,w);
	V(mutex_y1);
	V(mutex_y2);
	..
}

假设T1先执行P(mutex_y1),开始访问y变量,T1进入了访问y的临界区,这时候T2执行P(mutex_y2),T2也进入了访问y的临界区
假设T1和T2此时正在访问,此时切换到P3.此时z已经被T2上锁了。T3就会被阻塞在P(mutex_z)处。除非T2访问完y和z,把mutex_z,mutex_y2都给释放了,然后T3被唤醒,由于mutex_y2已经恢复,因此P(mutex_y2)不会阻塞,但是此时由于T1线程还没有释放mute_y1,因此T3进程会被阻塞在P(mutex_y1)处。等到T1访问完y之后,才把T3唤醒,此时T3就可以正常访问y和z了。

【例题18】

在这里插入图片描述
【知识点回顾】哲学家进餐问题
在这里插入图片描述
在这里插入图片描述

semaphore chopstick[5]  = {1,1,1,1,1};
semaphore max = 4;//最多允许4个哲学家进程同时吃饭
Pi() //i号哲学家的进程
{
    while(1)
    {
        P(max);
        P(chopstick[i]);
 	    P(chopstick[(i + 1) % 5]);
        吃饭...
        V(max);
        V(chopstick[i]);
        V(chopstick[(i + 1) % 5]); 
    }
}

类似地,中心的碗相当于限制哲学家的max
回顾传统的哲学家问题,假设餐桌上有n名哲学家、n根筷子,那么可以用这种方法避免死锁。其中一种方法就是限制允许最多n-1名哲学家同时抢筷子,那么至少会有一名哲学家可以获得两根筷子并顺利进餐,于是不可能发生死锁的情况。
本题可以用碗这个限制资源来避免死锁:当碗的数量m小于哲学家的数量n时,可以直接让碗的资源量等于m,确保不会出现所有哲学家都拿一侧筷子而无限等待另一侧筷子进而造成死锁的情况;当碗的数量m大于等于哲学家的数量n时,为了让碗起到同样的限制效果,我们让碗的资源量等于n-1.这样就能保证最多只有n-1名哲学家同时进餐,所以得到碗的资源量为min{n-1,m}.在进行PV操作时,碗的资源量起限制哲学家取筷子的作用,所以需要先对碗的资源量进行P操作。具体过程如下:

semaphore bowl;//用于协调哲学家对碗的使用
semaphore chopsticks[n];//用于协调哲学家对筷子的使用
for(int i = 0;i < n;i++)
{
	chopsticks[i] = 1;//设置两名哲学家之间筷子的数量
}
bowl = min(n - 1,m);//bowl <= n - 1,确保不死锁

Cobegin
{
	Pi() //i号哲学家的进程
	{
		P(bowl);//拿一个碗
		P(chopstick[i]);//拿左边筷子
		P(chopstick[(i + 1) % 5];//拿右边筷子
		吃饭..
		V(bowl);//把碗放回
		V(chopstick[i]);//放左边筷子
		V(chopstick[(i + 1) % 5];//放右边筷子
		思考...
	}
} Coend;
	

【例题19】

在这里插入图片描述
【解答】
本题只是要求实现操作的先后顺序,没有互斥关系,是一个简单的同步问题。
本题虽然有5个操作,但是只有4个同步关系。
画出前驱图,按照前V后P的原则写回代码。
在这里插入图片描述
在这里插入图片描述

【例题20】

在这里插入图片描述
【解析】
1)系统中会有多个进程并发运行,比如说P1和P2两个进程此时并发运行,它们都想要使用这个信号量,假设这个信号量S的初始值为1,当这两个进程并发执行的时候,就有可能会出现这样的情况,P1进程先上处理机运行,在while(S<=0)处检查当前S的值不是小于等于0的,所以P1进程可以顺利地跳出循环,顺利地执行后面的 S = S - 1. 但是在执行while(S<=0)时,P2进程此时也上处理机运行,同样先检查S当前的值,S此时的值时1,所以P2也可以顺利地跳出这个循环执行下一个语句,当两个进程都执行了S = S - 1之后,就会导致S的值变为-1.这就使得两个进程同时进入到临界区,所以我们对信号量S的访问必须是互斥的进行的。
2)方法一是不正确的。假设信号量S的初值为0,当P1进程用wait操作申请使用信号量的时候,会关中断,检测S的值是否小于等于0,如果小于等于0,那么这个while(S<=0)的循环就会一直卡住,而由于此时P1进程已经关中断了,P1进程就可以一直霸占处理机,因为只要关中断,那么就意味着进程切换不可能发生,这就会导致当信号量为0的时候一个进程就会被一直卡在while循环这儿,同时这个进程会一直霸占处理机,这样的话整个系统就无法往下推进了。
对于方法二,假设现在信号量的值为0,在申请使用信号量的时候,首先会关中断。然后while(S<=0)检查S的值是否小于等于0,发现满足条件,会先执行开中断,然后再执行关中断。然后再继续进行while循环,这就意味着当一个进程开中断了之后,在开中断和关中断这两个指令之间有可能发生进程切换,进程虽然会一直卡在while循环这里,即便S的值是0,由于循环过程中会有开中断,因此在开中断的时机就可以发生进程切换,假设有另一个进程P2进程执行了对S的signal操作,然后再切换为刚才卡住的进程,则被卡住的进程就可以顺利地跳出循环来使用S这个信号量了。

3)开/关中断指令属于特权指令,不允许用户进程使用。

【标准答案】

  1. 信号量S是能被多个进程共享的变量,多个进程都可以通过wait()和signal()对S进行读写操作。所以,wait()和signal()操作中对S的访问必须是互斥的。

  2. 方法1错误。在wait(),当S <= 0时,关中断后,其他进程无法修改S的值,while语句陷入死循环。
    方法2正确,方法2在循环体中有一个开中断的操作,这样就可以使其他进程修改S的值,从而避免while语句陷入死循环。

  3. 用户程序不能使用开中断/关中断指令来实现临界区互斥。因为开中断/关中断指令都是特权指令,不能在用户态下执行,只能在内核态下执行。

  • 40
    点赞
  • 283
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吃掉你的脑子·

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值