零、温馨提示
练习题俺觉得你实在闲再看(ps:我不信11408有闲人 TT)
一、练习题(王道课后题)
面包师有很多面包,由n名销售人员推销。每名顾客进店后按序取一个号,并且等待叫号,当一名销售人员空闲时,就按序叫下一个号。可以用两个整型变量来记录当前的取号值和叫号值,试设计一个使销售人员和顾客同步的算法。
分析:取号和叫号之间应该是一个同步关系吧,即我取完号了,销售人员后续才能喊。然后还有关于每个号,一个顾客拿了之后就不能再给其他顾客了,所以对于一个单一的号码,顾客之间对其访问应该是互斥的。而对于销售人员来说亦是如此,如果有一个销售小哥喊了号,就不允许其他小哥喊一样的号了,因此这个也是互斥的)
semaphore mutex_i=1,mutex_j=1;
int i=0,j=0;
Consumer(){
//走进面包店
P(mutex_i); //互斥的访问号码i(所取的号码)
i++;
V(mutex_i);
}
Seller(){
while(1){
P(mutex_j); //互斥的访问号码j(要喊的号码)
if(j<i){ //此时有人在等待
//叫号
j++;
V(mutex_j);
//卖掉面包
}
else{ //这会儿没人要这个号(没有顾客在等待)
V(mutex_j);
//休息片刻
}
}
}
某工厂有两个生产车间和一个装配车间,两个生产车间分别生产A,B两种零件,装配车间的任务是把A,B两种零件组装成产品。两个生产车间每生产一个零件后,都要分别把它们送到专配车间的货架F上。F1存放零件A,F2存放零件B,F1和F2的容量均可存放10个零件。装配工人每次从货架上取一个零件A和一个零件B后组装成产品。请用PV操作进行正确管理。
分析:我们先想想能不能往之前的板子PV上套。如果我们单看一条线,这是不是其实就是一个缓冲区为10的消费者——生产者问题呢。那变成两条线一下多了什么问题呢?我们看到工人是需要既有A又有B才能组装成产品的。
semaphore full1=0,full2=0;
semaphore empty1=10,empty2=10;
semaphore mutex1=1,mutex=1; //用于互斥地访问货架
PlantA(){
//生产了一个产品A
P(empty1);
p(mutex1);
//将产品A放到货架F1上
V(mutex1);
V(full1);
}
PlantB(){
//生产了一个产品B
P(empty2);
p(mutex2);
//将产品B放到货架F2上
V(mutex2);
V(full2);
}
Workshop(){
P(full1);
p(mutex1);
//将产品A从货架F1上拿走
V(mutex1);
V(empty1); //又产生了一格新的货架空间
P(full2);
p(mutex2);
//将产品B从货架F2上拿走
V(mutex2);
V(empty2); //又产生了一格新的货架空间
//将AB组装成产品
}
某寺庙有小和尚、老和尚若干,有一水缸,由小和尚提水入缸供老和尚饮用。水缸可容10桶水,水取自同一井中。水井径窄,每次只能容一个桶取水。水桶总数为3个。每次入缸取水仅为1桶水,且不可同时进行。试给出有关从缸取水、入水的算法描述。
分析:抓一下重点先,水缸一次只能容纳一个桶取水来着,所以水井其实是一个临界资源对吧(设置一个互斥信号量,初值为1),那我们再看水桶只有三个,那是不是不管你小和尚有多少个,最多只有三个人能拿到水桶对吧。(设置一个信号量cask,初值为3),然后看到老和尚那边是不是又变回生产者消费者问题了。水缸也是一个临界资源(设置一个互斥信号量,初值为1),然后就是熟悉的empty(初值为10)和full(初值为0)了。本题的关键是需要将散乱的同步和互斥关系串联起来,首先,得是水缸里有空余容量时,小和尚才需要“拎”桶起来去打水,从他拎起来桶的那一刻起,场上的桶数量就少1了,紧接着互斥地(交替地)打完水。再跑回来互斥地(交替地)倒入水缸。还有一点呢,就是作为德高望重的老和尚,咱不能那么粗鲁,咱也是得拿一个桶水打出来再喝,不能一头扎进去游泳啊。
semaphore cask=3;
semaphore well=1,jar=1;
semaphore full=0,empty=10;
Mock(){
while(1){
P(empty); //水缸有空余位置的时候,才需要打水
P(cask); //拿起来一个桶
P(well); //互斥地访问水井
//从水井打水
V(well);
P(jar); //互斥地访问水缸
//将水倒入水缸中
V(jar);
V(full);
V(cask);
}
}
Elder Mock(){
while(1){
P(full);
P(cask);
P(jar); //互斥地访问水缸
//从水缸里打一桶水
V(jar);
V(empty);
V(cask);//喝掉一桶水,水缸的容量加一
}
}
如下图所示,三个合作进程P1,P2,P3,它们都需要通过同一设备输入各自的数据a,b,c,该输入设备必须互斥地使用,而且其第一个数据必须由P1进程读取,第二个数据必须由P2进程读取,第三个数据必须由P3进程读取。然后,三个进程分别对输入数据进行下列计算:
P1:x=a+b P2:y=a*b P3:z=y+c-a
最后,P进程通过所连接的打印机将计算结果x,y,z的值打印出来。请用信号量实现它们的同步。
分析: 这个就比较简单啦,就是中间三对同步关系嘛,那我们就设置三个型号量S1(1)、S2(0)、S3(0),因为这样就不会同时使用输入设备了(那也就不用再多设置一个互斥信号量了)。然后我们再看图!只有P1这个老大能用打印机,那该怎么知道P2、P3中的操作已经完成了呢,当然还是要设置信号量啦(Sy,Sz初值都为0)。再然后我就中陷阱了,丫的abc还是分开输入的,那还得再来个Sb(我真是sb,初值也为0)。
semaphore S1=1,S2=0,S3=0;
semaphore Sy=0,Sz=0,Sb=0;
P1(){
P(S1);
从输入设备输入数据a;
V(S2);
P(Sb);
x=a+b;
P(Sy)
P(Sz);
使用打印机打印出x,y,2的结果;
}
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);
}
有桥如下图所示。车流方向如箭头所示。回答如下问题
1)假设桥上每次只能有一辆车行驶,试用信号灯的PV操作实现交通管理。
2)假设桥上不允许两车交会,但允许同方向多辆车一次通过(桥上可有多辆同方向行驶的车)。试用信号灯的PV操作实现桥上的交通管理。
分析:毋庸置疑桥肯定是一个临界资源对吧,需要设置一个互斥信号量bridge,这第一题就做完咯,再看第二题,只允许同方向多辆车通过,那意思是不是需要给个计数器,只有一方真的没车了,才允许另一边发车。是不是就感觉题目其实就变成了多读者——一写者的变体了。
semaphore bridge=1;
North(){
P(bridge);
//行驶车辆;
V(bridge);
}
South(){
P(bridge);
//行驶车辆;
V(bridge);
}
semaphore bridge=1;
semaphore NtoS=0,StoN=0;
int cnt1=0,cnt2=0;
North(){
while(1){
P(NtoS); //桥的每一边对于每一个cnt是互斥使用的,为了避免给桥加锁两次。
if(cnt1==0)
P(bridge); //桥两边对桥的使用是互斥的
cnt1++;
V(NtoS);
P(NtoS);
cnt1--;
if(cnt1==0)
V(bridge); //只有当一边真的没有车想要过桥的时候,才会让出使用权
V(NtoS);
}
}
South(){
while(1){
P(StoN);
if(cnt2==0)
P(bridge);
cnt2++;
V(StoN);
P(StoN);
cnt2--;
if(cnt2==0)
V(bridge);
V(StoN);
}
}
假设有两个线程(编号为0和1)需要去访问同一个共享资源,为避免竞争状态的问题,我们必须实现一种互斥机制,使得在任何时候只能有一个线程访问这个资源。假设有如下一段代码:
bool flag[2]; //flag数组,初始化为FALSE Enter_Critical_section(int my_thread_id,int other_thread_id){ while(flag[other_thread_id]==TRUE); //空循环语句 flag[my thread_id]=TRUE; } Exit_Critical_Section(int my_thread_id,int other_thread_id){ flag[my_thread_id]=FALSE; }
当一个线程想要访问临界资源时,就调用上述的这两个函数。
例如,线程0的代码可能是这样的:
Enter_Critical_Section(0,1);
使用这个资源;
Exit_Critical_Section(0,1);
做其他的事情;
试问:
1)以上的这种机制能够实现资源互访问吗?为什么?
2)若把Enter_Critical_Section函数中的两条语句互换位置,可能会发生死锁吗?
分析:语句都很简单蛤,就是不停的问问问看看有没有别人在使用这个共享资源,问一圈没有人用的话,就自己用。看起来貌似是实现了互斥的使用啊,但是问也是要时间的啊,万一大家刚好一块问的,又刚好擦肩而过的时候都没有人在使用,那这不是就没有达成想要的效果了吗。
然后看回死锁,死锁是什么呢?
死锁(Deadlock)指的是多个进程(或线程)在运行过程中因争夺资源而造成的一种僵局。在这种僵局中,每个进程都在等待其他进程释放资源,但没有任何一个进程能够继续执行,因为它们需要的资源都被其他进程占用着。
死锁的四个必要条件:
- 互斥条件(Mutual Exclusion):资源不能被多个进程同时使用,只能由一个进程使用。
- 占有和等待条件(Hold and Wait):进程至少持有一个资源,并且在等待其他进程释放它们持有的资源。
- 不可剥夺条件(No Preemption):已经分配给一个进程的资源,在该进程使用完之前,不能被强行夺走。
- 循环等待条件(Circular Wait):存在一种进程资源的循环等待关系,即进程A等待B的资源,B等待C的资源,...,形成一个循环。
这不是一看,包会的嘛。
参考答案:
1)这种机制不能实现资源的互访问。考虑如下情形:
①初始化时,flag数组的两个元素值均为FALSE。
②线程0先执行,执行while循环语句时,由于flag[1]为FALSE,所以顺利结束,不会被卡住。假设这时来了一个时钟中断,打断了它的运行。
③线程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循环语句的地方被卡住,因此这两个线程都无法执行下去,从而死锁。
设自行车生产线上有一个箱子,其中有N个位置(N>3),每个位置可存放一个车架或一个车轮;又设有3名工人,其活动分别为:
工人1活动: do{ 加工一个车架; 车架放入箱中; }while(1) 工人2活动: do{ 加工一个车轮; 车轮放入箱中; }while(1) 工人3活动: do{ 箱中取一个车架; 箱中取二个车轮; 组装为一台车; }while(1)
试分别用信号量与PV操作实现三名工人的合作,要求解中不含死锁。
分析:很容易就可以看出工人3明显得是排在工人1和工人2之后的对吧。那其实这不本质上还是生产者、消费者问题吗?最开始我我刷刷刷就写出了下面的代码。不过突然看到一句话,要求解中不含死锁?欸~难道在这过程中可能会出现死锁吗?(要是工人一做事情比较快,箱子里面全部都是轮子。亦或者是工人二做事情比较快,那箱子里都是轮子,导致工人三不就组装不成自行车,也就释放不了空间,于是工人一和工人二都只能在阻塞状态。这不就是死锁了吗?)
但其实也很好解决就是空位置的设计,我们让车轮子最多最多只能生产n-2个,同样的车架子最多最多只能生产n-1个,那问题不就迎刃而解了吗。
semaphore empty = n; // 箱子的空位置数
semaphore empty1 = n-2;
semaphore empty2 = n-1;
semaphore wheel = 0; // 车轮计数信号量
semaphore frame = 0; // 车架计数信号量
semaphore mutex = 1; // 互斥信号量
// 工人1:制造车架
Worker1() {
do {
// 制造好了一个车架
P(empty1); // 检测是否已经达到最大数
P(empty); // 等待空位置
P(mutex); // 进入临界区
// 将车架放入箱中
V(mutex); // 离开临界区
V(frame); // 增加车架信号量
} while (true);
}
// 工人2:制造车架
Worker2() {
do {
// 制造好了一个车轮
P(empty2); // 检测是否已经达到最大数
P(empty); // 等待空位置
P(mutex); // 进入临界区
// 将车轮放入箱中
V(mutex); // 离开临界区
V(wheel); // 增加车轮信号量
} while (true);
}
// 工人3:组装自行车
Worker3() {
do {
P(frame); // 等待一个车架
P(mutex); // 进入临界区
// 取出一个车架
V(mutex); // 离开临界区
V(empty);
V(empty1);
P(wheel); // 等待第一个车轮
P(wheel); // 等待第二个车轮
P(mutex); // 进入临界区
// 取出两个轮子
V(mutex); // 离开临界区
V(empty); // 释放一个空位置
V(empty); // 释放第二个空位置
V(empty2);
V(empty2);
// 将其组装成一辆自行车
} while (true);
}
设P,Q,R共享一个缓冲区,P,Q构成一对生产者-消费者,R既为生产者又为消费者,若缓冲区为空,则可以写入;若缓冲区不空,则可以读出。使用P,V操作实现其同步。
分析:要实现P,Q显然是很简单的,就是套个板子就好了。可这个R既为生产者又为消费者,该怎么处理呢?
它看到有东西可以读出来对吧,没东西它也可以写进去。那它很厉害呀,没有什么可以干预它的,我们只要保证和别的进程互斥就好了呀。
semaphore mutex = 1; // 互斥信号量,用于保护缓冲区
semaphore empty = 1; // 缓冲区的空位数
semaphore full = 0; // 缓冲区的已填充项数
P() { // 生产者
while (1) {
P(empty); // 等待空位
P(mutex); // 进入临界区
//Product One
V(mutex); // 退出临界区
V(full); // 增加已填充项数
}
}
Q() { // 消费者
while (1) {
P(full); // 等待有产品
P(mutex); // 进入临界区
//Consume One
V(mutex); // 退出临界区
V(empty); // 增加空位
}
}
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); // 增加空位
}
}
注:我个人理解觉得该题王道答案不太好其实,拿信号量当作判断条件应该是不太好的吧。
理发店里有一位理发师、一把理发椅和n把供等候理发的顾客坐的椅子。若没有顾客理发师便在理发椅上睡觉,一位顾客到来时,顾客必须叫醒理发师,若理发师正在理发时又有顾客来到,若有空椅子可坐,则坐下来等待,否则就离开。试用P,V操作实现,并说明信号量的定义和初值。
分析:首先对于顾客来说,这个n把椅子是不是其实就是缓冲区,只不过这题不一样的是如果此时没有一个人在等待的话,你可以把睡觉的那个臭小子叫起来为你服务;如果有人在等待的话,则判断是否还有地方坐,有地方坐就坐下来等,没地方坐就只能灰溜溜的离开了。
semaphore barber = 0;//表示理发师是否正在工作。初始值为0,表示理发师一开始在睡觉(不工作)。
semaphore consumers = 0;//表示当前在理发店里等待的顾客数量。初始值为0。
semaphore mutex = 1;//互斥信号量,用于保护临界区内共享资源(这里是line变量)的访问。初始值为1,表示没有其他同步需求,只有一个理发师。
int line = 0;//表示当前理发店里的顾客数量(包括正在理发的和等待的)。
int chairs = n;//表示理发店里供顾客等候的椅子数量。
Barber(){
while(1){
P(comsumers);
P(mutex);
line--;
V(barber);
V(mutex);
//Tony 工作中
}
Consumer(){
P(mutex);
if(line<chairs){
line++;
V(comsumers);
V(mutex);
P(barber);
//叫Tony来剪一个帅气的头型
}else{
V(mutex);
//转身离开,有话说不出来~
}
}
假设一个录像厅有1,2,3三种不同的录像片可由观众选择放映,录像厅的放映规则如下:
1)任意时刻最多只能放映一种录像片,正在放映的录像片是自动循环放映的,最后一名观众主动离开时结束当前录像片的放映。
2)选择当前正在放映的录像片的观众可立即进入,允许同时有多位选择同一种录像片的观众同时观看,同时观看的观众数量不受限制。
3)等待观看其他录像片的观众按到达顺序排队,当一种新的录像片开始放映时,所有等待观看该录像片的观众可依次序进入录像厅同时观看。用一个进程代表一个观众,要求:用信号量方法PV操作实现,并给出信号量定义和初始值。
分析: 这题看上去真的是非常难的亚子,字居然这么多。让我们来捋捋逻辑影片是自动循环播放的那其实只要用三对PV操作就可以实现对不对,那么如何做到来看自己想看电影的人又恰好在播放的时候能走进去呢,是不是有点像多读者,第一个读者来的时候如果是是允许读的那就读,后续最后一个人走的时候关闭。(要理解PV其实就可以实现循环了哦,不需要天天while)
// 信号量定义
semaphore movie[3] = {1, 1, 1}; // 控制对录像片1、2、3的访问,初始值为1
semaphore playing = 1; // 控制录像片的播放状态,初始值为1,表示可以开始播放
// 计数器定义
int c1 = 0; // 当前观看录像片1的观众数量
int c2 = 0; // 当前观看录像片2的观众数量
int c3 = 0; // 当前观看录像片3的观众数量
void Filmgoer1() {
P(movie[0]); // 进入临界区
if (c1 == 0) {
P(playing); // 如果没有观众,开始播放
}
c1++; // 增加观看人数
V(movie[0]); // 退出临界区
// Watching movie
// 模拟观看时间
P(movie[0]); // 进入临界区
c1--; // 减少观看人数
if (c1 == 0) {
V(playing); // 如果是最后一位观众,停止播放
}
V(movie[0]); // 退出临界区
}
void Filmgoer2() {
P(movie[1]); // 进入临界区
if (c2 == 0) {
P(playing); // 如果没有观众,开始播放
}
c2++; // 增加观看人数
V(movie[1]); // 退出临界区
// Watching movie
// 模拟观看时间
P(movie[1]); // 进入临界区
c2--; // 减少观看人数
if (c2 == 0) {
V(playing); // 如果是最后一位观众,停止播放
}
V(movie[1]); // 退出临界区
}
void Filmgoer3() {
P(movie[2]); // 进入临界区
if (c3 == 0) {
P(playing); // 如果没有观众,开始播放
}
c3++; // 增加观看人数
V(movie[2]); // 退出临界区
// Watching movie
// 模拟观看时间
P(movie[2]); // 进入临界区
c3--; // 减少观看人数
if (c3 == 0) {
V(playing); // 如果是最后一位观众,停止播放
}
V(movie[2]); // 退出临界区
}
设公共汽车上驾驶员和售票员的活动分别如下图所示。驾驶员的活动:启动车辆,正常行车,到站停车:售票员的活动:关车门,售票,开车门。在汽车不断地到站、停车、行驶的过程中,这两个活动有什么同步关系?用信号量和PV操作实现它们的同步。
一组进程的执行顺序如下图所示,圆圈P1,P2,P3,P4,P5,P6表示进程,弧上的字母a,b,c,d,e,f,g,h表示同步信号量,请用P,V操作实现进程的同步。
分析:我们很容易就能看到这张图中有着许许多多的同步关系,那其实只需要仿照书中的代码,小小照猫画虎一下即可。
//一共有多少对同步关系是不是其实就只要看有多少根连线即可
semaphore a=b=c=d=e=f=g=h=0;
P1(){
V(a);
V(b);
}
P2(){
P(a);
V(c);
V(d);
}
P3(){
P(b);
V(e);
V(f);
}
P4(){
P(e);
P(c);
V(g);
}
P5(){
P(f);
P(d);
V(h);
}
P6(){
P(h);
P(g);
}
假设有3个抽烟者和1个供应者。每个抽烟者不停地卷烟并抽掉它,但要卷起并抽掉一支烟,抽烟者需要有三种材料:烟草、纸和胶水。三个抽烟者中,第一个拥有烟草,第二个拥有纸,第三个拥有胶水。供应者无限提供三种材料,供应者每次将两种材料放到桌子上,拥有剩下那种材料的抽烟者卷一根烟并抽掉它,并给供应者一个信号告诉已完成,此时供应者就将另外两种材料放到桌上,如此重复,让3个抽烟者轮流抽烟。
懒得写啊啊啊
有3个进程P1、P2、P3合作处理数据,P从输入设备读数据到缓冲区,缓冲区可存1000个字。P1和P2的功能一样,都是从缓冲区取出数据并计算,再打印结果。请用信号量的P,V操作实现。其中,语句read()从输入设备读入20个字到缓冲区;get()从缓冲区取出20个字;comp()计算40个字输出并得到结果的1个字;print()打印结果的2个字。
二、真题(往年)
2009
【2009统考真题】三个进程P1,P2,P3互斥使用一个包含N(N>0)个单元的缓冲区。P1每次用produce()生成一个正整数并用put()送入缓冲区某一空单元;P2每次用getodd()从该缓冲区中取出一个奇数并用countodd()统计奇数个数;P3每次用geteven()从该缓冲区中取出一个偶数并用counteven()统计偶数个数。请用信号量机制实现这三个进程的同步与互斥活动,并说明所定义的信号量的含义(要求用伪代码描述)。
分析:正如题目中所描述的其实已经相当明朗了对吧,就是生产者消费者的变体,但需要额外考虑的是生产出来的究竟是偶数还是奇数,然后再通知另外的两个进程来取走各自想要的数字。
要注意的就是给定函数的使用。
semaphore mutex = 1;
semaphore empty = N; // 空槽位数
semaphore cntO = 0; // 奇数计数
semaphore cntE = 0; // 偶数计数
P1() {
while (true) {
P(empty); // 等待空槽
P(mutex); // 进入临界区
int number = Produce(); // 生成一个数字
puts(number); // 将数字存储在缓冲区
if (number % 2 != 0) { // 检查数字是否为奇数
V(cntO); // 信号表示有一个奇数
} else {
V(cntE); // 信号表示有一个偶数
}
V(mutex); // 退出临界区
}
}
P2() {
while (true) {
P(cntO); // 等待一个奇数
P(mutex); // 进入临界区
int number = getsodd(); // 从缓冲区获取奇数
countodd(); // 计算奇数
V(mutex); // 退出临界区
V(empty); // 信号表示有空槽
}
}
P3() {
while (true) {
P(cntE); // 等待一个偶数
P(mutex); // 进入临界区
int number = geteven(); // 从缓冲区获取偶数
counteven(); // 计算偶数
V(mutex); // 退出临界区
V(empty); // 信号表示有空槽
}
}
2011
【2011统考真题】某银行提供1个服务窗口和10个供顾客等待的座位。顾客到达银行时,若有空座位,则到取号机上领取一个号,等待叫号。取号机每次仅允许一位顾客使用。当营业员空闲时,通过叫号选取一位顾客,并为其服务。顾客和营业员的活动过程描述如下:
cobegin { process 顾客i { 从取号机获取一个号码; 等待叫号; 获取服务; } process 营业员 { While(TRUE) { 叫号; 为客户服务; } } }coend
请添加必要的信号量和P,V[或wait(),signal()]操作,实现上述过程中的互斥与同步。要求写出完整的过程,说明信号量的含义并赋初值。
分析:做过很类似的叫号问题蛤,首先得给每个号码都套上一个信号量互斥的访问(因为一个号码只能由一个人拿到)。然后分别设置两个整型变量用于记录当前已经取走的号码和当前已经喊到的号码。最后再看回座位其实也就是缓冲区对吧。
semaphore empty = 10; // 可用号码的数量
semaphore mutex = 1; // 互斥信号量
semaphore full = 0; // 已取号的顾客数量
semaphore service = 0; // 服务信号量
cobegin
{
process 顾客i
{
P(empty); // 等待有空位
P(mutex); // 进入临界区
从取号机获取一个号码; // 获取号码
V(mutex); // 离开临界区
V(full); // 增加已取号顾客数量
P(service); // 等待服务
获取服务; // 获取服务
}
process 营业员
{
While(TRUE)
{
P(full); // 等待有顾客
V(empty); // 增加可用号码
V(service); // 叫号并准备服务
为顾客服务; // 为顾客提供服务
}
}
} coend
注:错误版本!!只是用于回顾自己很容易犯错的点,以下程序没有考虑到当没有顾客的时候营业员应该干什么。
semaphore empty=10;
int i=0,j=0;
semaphore mutexi=1,mutexj=1;
semaphore serviceWindow=1;
cobegin
{
process 顾客i
{
P(empty);
P(mutexi);
从取号机获取一个号码;
i++;
V(mutexi)
P(serviceWindow);
等待叫号;
获取服务;
V(empty)
}
process 营业员
{
While(TRUE)
{
P(mutexj);
叫号;
if(j>i){
V(mutexj);
}
else{
为客户服务;
j++;
V(serviceWindow);
V(mutexj);
}
}
}
}coend
2013
【2013统考真题】某博物馆最多可容纳500人同时参观,有一个出入口,该出入口一次仅允许一人通过。参观者的活动描述如下:
cobegin { 参观者进程i { ... 进门; ... 参观; ... 出门; ... } }coend
请添加必要的信号量和P,V[或wait(),signal()]操作,以实现上述过程中的互斥与同步。要求写出完整的过程,说明信号量的含义并赋初值。
分析:馆内一共只有500个人的空间,而门口就只有一个,且还只能一个人过。这表明这个门口就是一个临界资源对吧。外面的人一定要里头有空位的时候才能往里面走对吧;而里头的人只需要门口没人就可以溜之大吉了对吧,那其实就很简单了。
semaphore empty=500;
semaphore door=1;
cobegin
{
参观者进程i
{
P(empty); //里头有位置
P(door); //门也没人用
进门;
V(door);
参观;
P(door); //不想看了,跑路前看看门口有没有人挡着
出门;
V(door);
V(empty); //跑出来一个人
}
}coend
2014
【2014统考真题】系统中有多个生产者进程和多个消费者进程,共享一个能存放1000件产品的环形缓冲区(初始为空)。缓冲区未满时,生产者进程可以放入其生产的一件产品,否则等待;缓冲区未空时,消费者进程可从缓冲区取走一件产品,否则等待。要求一个消费者进程从缓冲区连续取出10件产品后,其他消费者进程才可以取产品。请使用信号量P,V(wait(),signal())操作实现进程间的互斥与同步,要求写出完整的过程,并说明所用信号量的含义和初值。
分析:老熟人了吧这种题,就是生产者消费者的变体对吧,唯一不一样的就是需要每一个消费者进程连续取走10件产品之后其他消费者进程才可以进来取产品,那其实就是设置一个整型变量用来记录当前取没取到10件不就好了吗。
semaphore empty = 1000 // 缓冲区中空位的信号量,初始值为缓冲区的最大容量
semaphore full = 0 // 缓冲区中产品的信号量,初始值为0
semaphore mutex = 1 // 互斥信号量,用于保护对缓冲区的互斥访问
semaphore consumer = 1 // 控制消费者进程的信号量,初始值为1
// 生产者进程
Producer(){
while(1){
P(empty) // 等待缓冲区有空闲位置
P(mutex) // 进入临界区,确保互斥访问缓冲区
生产产品并放入缓冲区
V(mutex) // 离开临界区,允许其他进程访问缓冲区
V(full) // 增加缓冲区中产品的信号量
}
}
// 消费者进程
Consumer()
while(1){
P(consumer) // 等待消费许可
int cnt = 0 // 计数器,记录一个消费者连续取出的产品数量
while(cnt < 10)
P(full) // 等待缓冲区有产品可以取出
P(mutex) // 进入临界区,确保互斥访问缓冲区
从缓冲区取走产品
V(mutex) // 离开临界区
V(empty) // 增加缓冲区中空位的信号量
cnt = cnt + 1 // 增加连续取出的产品计数
}
V(consumer) // 允许其他消费者进程消费
}
}
2015
【2015统考真题】有A,B两人通过信箱进行辩论,每个人都从自己的信箱中取得对方的问题。将答案和向对方提出的新问题组成一个邮件放入对方的邮箱中。假设A的信箱最多放M个邮件,B的信箱最多放N个邮件。初始时A的信箱中有x个邮件(0<x<M),B的信箱中有y个邮件(0<<N)。辩论者每取出一个邮件,邮件数减1。A和B两人的操作过程描述如下:
当信箱不为空时,辩论者才能从信箱中取邮件,否则等待。当信箱不满时,辩论者才能将新邮件放入信箱,否则等待。请添加必要的信号量和P,V[或wait,signal]操作,以实现上述过程的同步。要求写出完整的过程,并说明信号量的含义和初值。
分析:其实本质上就还是生产者消费者问题吧,就只是说需要稍稍微加工一下,不一样的点就在于AB两方是不是都既是消费者又是生产者,然后两个信箱又都是缓冲区,那怎么办?是不是设置两个用于互斥访问的信号量呗(mutexA,mutexB)然后分别为各自弄上full和empty这题是不是就搞定啦。
semaphore Full_A = x; //FullA表示A的信箱中的邮件数量
semaphore Empty_A = M-x; //EmptyA表示A的信箱中还可存放的邮件数量
semaphore Full_B = y; //FullB表示B的信箱中的邮件数量
semaphore Empty_B = N-y; //Empty_B表示B的信箱中还可存放的邮件数量
semaphore mutex A = 1; //mutexA用于A的信箱互斥
semaphore mutex B = 1; //mutexB用于B的信箱互斥
A{
while(TRUE){
P(Full_A);
P(mutex_A);
从A的信箱中取出一个邮件;
V(mutex_A);
V(Empty_A);
回答问题并提出一个新问题;
P(Empty_B);
P(mutex_B);
将新邮件放入B的信箱;
V(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);
}
}
2017
分析:没好好学OS还真容易掉到这个坑里噢t1和t2的add()是不是只是读了一下两个参数并没有对其内在改变呢,所以读的话是不是应该是可以一块的,但是读和写是不是不行
semaphore y1=1; // 信号量y1,用于控制thread1与thread3对共享资源y的互斥访问
semaphore y2=1; // 信号量y2,用于控制thread2与thread3对共享资源y的互斥访问
semaphore z=1; // 信号量z,用于控制对共享资源z的访问
thread1{
cnum w;
wait(y1); // 等待信号量y1,确保线程1可以进入临界区
w=add(x,y); // 执行加法操作,将结果存储在w中
signal(y1); // 释放信号量y1,允许其他线程进入临界区
...
}
thread2{
cnum w;
wait(y2); // 等待信号量y2,确保线程2可以进入临界区
wait(z); // 等待信号量z,确保线程2可以访问共享资源z
w=add(y,z); // 执行加法操作,将结果存储在w中
signal(z); // 释放信号量z,允许其他线程访问共享资源z
wait(y2); // 再次等待信号量y2,确保线程2离开临界区
...
}
thread3{
cnum w;
w.a=1; // 初始化w的a属性为1
w.b=1; // 初始化w的b属性为1
wait(z); // 等待信号量z,确保线程3可以访问共享资源z
z=add(z,w); // 执行加法操作,将结果存储在z中
signal(z); // 释放信号量z,允许其他线程访问共享资源z
wait(y1); // 等待信号量y1,确保线程3可以进入临界区
wait(y2); // 等待信号量y2,确保线程3可以进入临界区
y=add(y,w); // 执行加法操作,将结果存储在y中
signal(y1);
signal(y2);
}
2019
分析:乍一看就是道哲学家就餐对吧,but我们会发现圆桌中心有m个碗!!那是不是现在条件就变复杂了,得有碗有筷子才能吃饭对吧,那很自然的想法是不是应该将碗定义成互斥变量,然后就变回我们普通的哲学家就餐问题了对吧,那那三种策略随便选一个就好了
semaphore bowl=w; // 信号量bowl,用于控制对碗的访问,初始值为w,表示碗的数量
semaphore mutex=1; // 信号量mutex,用于保护临界区,确保同时只有一个哲学家可以进入临界区
semaphore chopstick[n]; // 信号量数组chopstick,用于控制对筷子的访问,每个筷子一个信号量
for(int i=0;i<n;i++){
chopstick[i]=1; // 初始化每个筷子的信号量,初始值为1,表示筷子可用
}
philosopher i{
while(1){
P(bowl); // 等待碗,确保哲学家可以开始就餐
P(mutex); // 进入临界区,确保哲学家可以安全地拿起筷子
P(chopstick[i%n]); // 拿起左边的筷子
P(chopstick[(i+1)%n]); // 拿起右边的筷子
恰饭; // 哲学家开始就餐
V(chopstick[(i+1)%n]); // 放下右边的筷子
V(chopstick[i%n]); // 放下左边的筷子
V(mutex); // 离开临界区,允许其他哲学家进入
V(bowl); // 哲学家就餐完毕,释放碗
}
}
看了答案发现自己还是有点菜了蛤,倒是有点轻视这道题目了(人家可是真题!!)还是这版答案优雅
最优解:
semaphore bowl=min(w,n-1); // 信号量bowl,用于控制对碗的访问,初始值设置为w和n-1中的较小值,避免死锁
semaphore chopstick[n]; // 信号量数组chopstick,用于控制对筷子的访问,每个筷子一个信号量
for(int i=0;i<n;i++){
chopstick[i]=1; // 初始化每个筷子的信号量,初始值为1,表示筷子可用
}
philosopher i{
while(1){
P(bowl); // 等待碗,确保哲学家可以开始就餐
P(chopstick[i%n]); // 拿起左边的筷子(i%n表示当前哲学家编号,%是取模运算)
P(chopstick[(i+1)%n]); // 拿起右边的筷子((i+1)%n表示下一个哲学家编号)
恰饭; // 哲学家开始就餐
V(chopstick[(i+1)%n]); // 放下右边的筷子
V(chopstick[i%n]); // 放下左边的筷子
V(bowl); // 哲学家就餐完毕,释放碗,允许其他哲学家就餐
}
}
2020
分析:也真的是最简单的一年了,以后应该没可能只考同步蛤,唯一要注意的就是写好信号量的定义和初值就好。
Semaphore SAC = 0; //控制操作 A 和 C 的执行顺序
Semaphore SBC = 0; //控制操作 B 和 C 的执行顺序
Semaphore SCE = 0; //控制操作 C 和 E 的执行顺序
Semaphore SDE = 0; //控制操作 D 和 E 的执行顺序
CoBegin
Begin
操作 A;
signal(SAC);
End
Begin
操作 B;
signal(SBC);
End
Begin
wait(SAC);
wait(SBC);
操作 C;
signal(SCE);
End
Begin
操作 D;
signal(SDE);
End
Begin
wait(SCE);
wait(SDE);
操作 E;
End
CoEnd
2021
1)信号量S是能被多个进程共享的变量,多个进程都可通过wait()和signal()对S进行读、写操作。所以,wait()和signal()操作中对S的访问必须是互斥的。
2)方法1错误。在wait()中,当S<=0时,关中断后,其他进程无法修改S的值,while语句陷入死循环。方法2正确。方法2在循环体中有一个开中断操作,这样就可以使其他进程修改S的值,从而避免while语句陷入死循环。
3)用户程序不能使用开/关中断指令实现临界区互斥。因为开中断和关中断指令都是特权指令,不能在用户态下执行,只能在内核态下执行。
注:记录型信号量和整数型信号量不一样哦。
就是没有实现让权等待,如果不做出处理的话最右边那个版本的初始值为小于等于0的数的话,就直接死循环了。
2022
46、某进程的两个线程 T1 和 T2 并发执行 A、B、C、D、E 和 F 共 6 个操作,其中 T1 执行 A、 E 和 F,T2 执行 B、C 和 D。下图表示上述 6 个操作的执行顺序所必须满足的约束;C 在 A 和 B 完成后执行,D 和 E 在 C 完成后执行,F 在 E 完成后执行。请使用信号量的 wait ()、signal () 操作描述 T1 和 T2 之间的同步关系,并说明所用信号量的作用及其初值。
分析:好好好,20年才说的,现在就打自己脸了,不过还是有点不一样的这个(真的不是我狡辩)
- T1:A、E、F
- T2:B、C、D
分为两喇之后再回头看图你会发现什么,欸因为BC是T2一个人干的,所以是不是在最开始就只要等A一执行完BCD刷刷刷就干完了,那同样的呀,E是不是只要等C整完也直接EF走了。
semaphore AC=0; // 信号量AC,用于控制T1和T2之间的同步,初始值为0
semaphore CE=0; // 信号量CE,用于控制T1和T2之间的同步,初始值为0
T1{
A; // 执行操作A
signal(AC); // 释放信号量AC,表示A已经完成,可以通知T2执行C
wait(CE); // 等待信号量CE,直到T2执行完C并释放CE
E; // 执行操作E
F; // 执行操作F
}
T2{
B; // 执行操作B
wait(AC); // 等待信号量AC,直到T1执行完A并释放AC
C; // 执行操作C
signal(CE); // 释放信号量CE,表示C已经完成,可以通知T1执行E
D; // 执行操作D
}
2023
45、现要求学生使用swap指令和布尔型变量lock实现临界区互斥。lock为线程间共享的变量,lock的值为TRUE时线程不能进入临界区,为FALSE时线程能够进入临界区。某同学编写的实现临界区互的伪代码如图所示
1)左图中伪代码哪些语句存在错误?将其改为正确的语句(不增加语句的条数)
2)右图给出了交换两个变量值的函数newSwap()的代码,是否可以用函数调用语句“newSwap(&key,&lock)代替指令"swap key,lock"以实现临界区互斥?为什么?
1)if(key==TRUE)------>while(key==TRUE) //存在错误,因为其无法其到一个互斥的作用(选择语句卡不住别人啊)
lock = TRUE;------>lock = FLASE; //不修改的话前面改的循环语句会死循环
2)当然不可以,swap指令由硬件执行,属于原子操作,不可被中断,但newSwap(&key,&lock)可以被多个线程并发执行
2024
46、计算机系统中的进程之同往往需要相互协作以完成一个任务。在某网络系统中,缓冲区B用于存放一个数据分组,对B的操作有C1、C2和C3。C1将一个数据分组写入B中,C2从B中读出一个数据分组,C3对B中的数据分组进行修改。要求B为空时才能执行C1,B非空时才能执行C2和C3。
请回答下列问题。
1)假设进程P1和P2均需要执行C1,实现C1的代码是否为临界区?为什么?
2)假设B初始空,进程P1执行C1一次,进程P2执行C2一次。请定义尽可能少的信号量,并用wait()、signal()操作描述进程P1和P2之间的同步或互斥关系,说明所用信号量的作用及其初值。
3)假设B初始不为空,进程P1和P2各执行C3一次。请定义尽可能少的信号量,并用wait()、signal()操作描述进程PI和P2之间的同步或互斥关系,说明所用信号量的作用及其初值。
分析:考考自己,临界区的含义是什么呢?模拟的时候咋一看还在怀疑是不是计网题太bt了送点好吃的,关键就是这个尽可能少。还有要注意的是人家要求的是wait()和signal(),别刷刷刷PV就干进去了。
1)两个进程都要写入肯定不行啊,B就一格位置呢,所以实现C1的代码需要为临界区,因为对于B的存操作需要互斥访问
2)
semaphore full=0; // 信号量full,用于控制两个进程之间的同步,初始值为0,表示缓冲区为空
P1{
...
C1; // 执行操作C1,向缓冲区添加数据
signal(full); // 释放信号量full,表示缓冲区不再为空,有数据可以消费
...
}
P2{
...
wait(full); // 等待信号量full,直到缓冲区中有数据可以消费
C2; // 执行操作C2,从缓冲区读取数据
...
}
3)
semaphore mutex=1; // 信号量mutex,用于控制对共享资源的互斥访问,初始值为1,表示资源未被占用
P1{
...
wait(mutex); // 等待信号量mutex,请求进入临界区
C3; // 执行操作C3,对共享资源的修改
signal(mutex); // 释放信号量mutex,离开临界区,允许其他进程进入
...
}
P2{
...
wait(mutex); // 等待信号量mutex,请求进入临界区
C3; // 执行操作C3,对共享资源的修改
signal(mutex); // 释放信号量mutex,离开临界区,允许其他进程进入
...
}