一、练习题
面包师有很多面包,由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(){//生产了一个产品AP(empty1);p(mutex1);//将产品A放到货架F1上V(mutex1);V(full1);
}PlantB(){//生产了一个产品BP(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 OneV(mutex); // 退出临界区V(full); // 增加已填充项数}
}Q() { // 消费者while (1) {P(full); // 等待有产品P(mutex); // 进入临界区//Consume OneV(mutex); // 退出临界区V(empty); // 增加空位}
}R() { // 双面人if(empty==1){P(empty); // 等待空位P(mutex); // 进入临界区//Product OneV(mutex); // 退出临界区V(full); // 增加已填充项数}if(full==1){P(full); // 等待有产品P(mutex); // 进入临界区//Consume OneV(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统考真题】三个进程P1,P2,P3互斥使用一个包含N(N>0)个单元的缓冲区。P1每次用produce()生成一个正整数并用put()送入缓冲区某一空单元;P2每次用getodd()从该缓冲区中取出一个奇数并用countodd()统计奇数个数;P3每次用geteven()从该缓冲区中取出一个偶数并用counteven()统计偶数个数。请用信号量机制实现这三个进程的同步与互斥活动,并说明所定义的信号量的含义(要求用伪代码描述)。
正如题目中所描述的其实已经相当明朗了对吧,就是生产者消费者的变体,但需要额外考虑的是生产出来的究竟是偶数还是奇数,然后再通知另外的两个进程来取走各自想要的数字。
semaphore mutex = 1;
semaphore empty = N; // Number of empty slots
semaphore cntO = 0; // Count of odd numbers
semaphore cntE = 0; // Count of even numbersP1() {while (true) {P(empty); // Wait for an empty slotP(mutex); // Enter critical sectionint number = Produce(); // Produce a numberputs(number); // Store the number in the bufferif (number % 2 != 0) { // Check if the number is oddV(cntO); // Signal there is an odd number} else {V(cntE); // Signal there is an even number}V(mutex); // Exit critical section}
}P2() {while (true) {P(cntO); // Wait for an odd numberP(mutex); // Enter critical sectionint number = getsodd(); // Get odd number from buffercountodd(); // Count the odd numberV(mutex); // Exit critical sectionV(empty); // Signal an empty slot}
}P3() {while (true) {P(cntE); // Wait for an even numberP(mutex); // Enter critical sectionint number = geteven(); // Get even number from buffercounteven(); // Count the even numberV(mutex); // Exit critical sectionV(empty); // Signal an empty slot}
}
【2011统考真题】某银行提供1个服务窗口和10个供顾客等待的座位。顾客到达银行时,若有空座位,则到取号机上领取一个号,等待叫号。取号机每次仅允许一位顾客使用。当营业员空闲时,通过叫号选取一位顾客,并为其服务。顾客和营业员的活动过程描述如下:
cobegin {process 顾客i{从取号机获取一个号码;等待叫号;获取服务;}process 营业员{While(TRUE){叫号;为客户服务;}} }coend
请添加必要的信号量和P,V[或wait(),signal()]操作,实现上述过程中的互斥与同步。要求写出完整的过程,说明信号量的含义并赋初值。
分析:做过很类似的叫号问题蛤,首先得给每个号码都套上一个信号量互斥的访问(因为一个号码只能由一个人拿到。然后分别设置两个整型变量用于记录当前已经取走的号码和当前已经喊到的号码。最后再看回座位其实也就是缓冲区对吧。
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统考真题】某博物馆最多可容纳500人同时参观,有一个出入口,该出入口一次仅允许一人通过。参观者的活动描述如下:
cobegin {参观者进程i{...进门;...参观;...出门;...} }coend
请添加必要的信号量和P,V[或wait(),signal()]操作,以实现上述过程中的互斥与同步。要求写出完整的过程,说明信号量的含义并赋初值。
【2014统考真题】系统中有多个生产者进程和多个消费者进程,共享一个能存放1000件产品的环形缓冲区(初始为空)。缓冲区未满时,生产者进程可以放入其生产的一件产品,否则等待:缓冲区未空时,消费者进程可从缓冲区取走一件产品,否则等待。要求一个消费者进程从缓冲区连续取出10件产品后,其他消费者进程才可以取产品。请使用信号量P,V(wait(),signal())操作实现进程间的互斥与同步,要求写出完整的过程,并说明所用信号量的含义和初值。