用信号量进程同步与互斥
1.理解生产者和消费者问题
没有引入信号量时的生产者和消费者进程,什么情况下会出现结果不唯一?什么情况下会出现永远等待?
用信号解决生产者和消费者的同步与互斥,要求能自己写出来。
答:sleep()和wakeup()是操作系统基本内核函数,他们被封装在函数库中供应用程序使用,执行sleep()时,让调用进程进入等资源队列,当资源可用时从相应队列释放等资源的进程。
结果不唯一:当进程之间共享变量counter,对counter的访问未加限制,生产者进程和消费者进程对counter的交替操作会使其结果不唯一,例如counter的当前值为8,如果生产者生产一件产品并投入缓冲区,拟执行counter加1操纵,同时消费者获取一个产品消费,拟执行counter减1操作,假如两者交替执行加1或减1操作,取决于其执行速度,counter的值可能是9,也可能是7,但是其正确值应该是8.
出现永远等待:假定消费者读取counter值时发现它为0,此时调度程序暂停消费者进程,让生产者进程运行,生产者加入一个产品,将counter的值加1,他想当然的推想由于counter值刚才为0,所以此时消费者一定是在睡眠,于是生产者调用wakeup()函数来唤醒消费者,遗憾的是,消费者尚未睡眠时唤醒信号丢失,当消费者下次运行时,因已测到counter的值为0,于是去睡眠,这样生产者迟早会填满缓冲区然后去睡眠,这样就造成了所有进程都永远处于睡眠状态。
PV操作:
item B[k];
semaphore empty;empty=k;
semaphore full;full=0;
semaphore mutex;mutex=1;
int in=0;
int out=0;
cobegin
process producer_i)(){
while(ture){
produce();
P(empty);
P(mutex);
apppend to B[in];
in=(in+1)%k;
V(mutex);
V(full);
}
process consumer_j(){
while(ture){
P(full);
P(Mutex);
take from B[out];
out=(out+1)%k;
V(muter);
V(empty);
consume();
}
}
}
2.哲学家吃面问题
semaphore fore[5];
for (int i=0;i<5;i++)
{
fork[i]=1;
}
cobegin
process philosopher_i(){
while(ture){
think();
P(fork[i]);
P(fork[(i+1)%5]);
eat();
V(fork[i]);
V(fork[(i+1)%5]);
}
}
coend
分析:显然,最多两个哲学家同时吃面,假如每个哲学家都拿起一只筷子,就会永久等待(死锁),下面介绍一种方法。
typedef int Semaphore; //定义信号量Semaphore类型是整型的别名 const int N = 5; //定义哲学家的数目,该数目可以根据需要修改 const LEFT (i-1)%N; //哲学家i的左邻 const RIGHT (i+1)%N; //哲学家i的右邻 enum status{Thinking,Hungry,Eating};//哲学家所处的三种状态的常量枚举 Semaphore mutex = 1; //定义一个互斥信号量,实现对临界区的互斥 semaphore s[N] = {0}; //每个哲学家对应的信号量,初值为0 int state[N]; //每个哲学家的状态数组
void TakeChopstick(int i) //第i个哲学家试图拿筷子 { P(mutex); //进入访问共享变量state[i]的临界区前对mutex的P操作 state[i]=Hungry; //设置状态为饥饿 test(i); //测试能不能取筷子 V(mutex); //起开临界区 P(s[i]); //如果没有筷子,进程阻塞 }
void PutChopstick(int i) //哲学家i放下筷子 { P(mutex); //访问临界区的互斥信号量 State[i]=Thinking; //吃完了,放下筷子进入思考状态 test(LEST); //唤醒左边哲学家拿筷子 test(RIGHT); //唤醒右边哲学家拿筷子 V(mutex); //离开临界区 }
void test(int i) { if(state[i]==Hungry && state[LEFT] != Eating &&state[RIGHT] !=Eating) { State[i]=Eating; V(s[i]); //唤醒哲学家进程,唤醒当前被阻塞的进程 } }
process Phliospher(int i) { while(1) { 思考; TakeChopstick(i); 吃面条; PutChopstick(i); } }
3.读写文件问题
单纯使用信号量不能解决此此问题,必须引入计数器readcount对读进程计数,mutex是用于对计数器readcount操作的互斥信号量,writerblock表示是否允许写的信号量。
int readcount=0;
semaphore writeblock,mutex;
writeblock=1;mutex=1;
cobegin
process reader_i(){ process writer_j(){
P(mutex); P(writeblock);
readcount++; V(writeblock);
if(readcount==1) }
P(writeblock);
V(mutex);
P(mutex);
readcount--;
if(readcount==0)
V(writeblock);
V(mutex);
}
coend
4.理发师问题
int waiting =0;
int CHAIRS=N;
semaphore customers,barbers,mutex;
customers=0;barber=0;mutex=1;
cobegin
process barbers(){
while(ture){
P(customers);
P(mutex);
waiting--;
V(barber);
V(mutex);
cutair();
}
}
process customer_i(){
P(mutex);
If(waiting<CHAIRS){
waiting++;
V(customers);
V(mutex);
P(barbers);
get_haircut();
}
else
V(mutex);
}
coend
第一队音乐爱好者要竞争“待出售的音乐磁带和电池”,而且在初始状态下,系统并无“待出售的音乐磁带和电池”,故可为该种资源设置一初值为0的信号量buy1,同样,需设置初值为0的buy2、buy3分别对应“待出售的随声听和电池”、"待出售的随声听和音乐磁带"。另外,为了同步买者的付费动作和给货动作,还需设置信号量payment和goods,以保证买者在付费后才能得到所需商品,信号量music overi用来同步音乐爱好者听乐曲和酒吧老板的下一次出售行为,具体如下:
semaphore buy1=buy2=buy3=0;
semaphore payment=0;
semaphore goods=0;
swmaphore music_over=0;
cobegin{
process boss(){//酒吧老板
while(ture){
拿出任意两种物品出售;
if(出售的是音乐磁带和电池) V (buy1);
else if(出售的是随声听和电池) V(buy2);
else if(出售的是随声听和音乐磁带) V(buy3);
P(payment);//等待支付
V(goods);//给货
P(music_over);//等待乐曲结束
}
}
process fan1(){//第1队音乐爱好者
while(ture){//因为一个进程代表一队,而不是一个爱好者,所以这里是//while(ture)
P(buy1);//等有音乐磁带和电池出售
V(payment);//付费
P(goods);//取货
欣赏一曲乐曲;
V(music_over);//通知老板乐曲结束
}
}
process fan2(){//第二队音乐爱好者
while(ture){
P(buy2); //等有随声听和电池出售
V(payment);//付费
P(goods);//取货
欣赏一曲乐曲;
V(music_over);//通知老板乐曲结束
}
}
process fan3(){//第三队音乐爱好者
while(ture){
P(buy3); //等有随声听和音乐磁带出售
V(payment);//付费
P(goods);//取货
欣赏一曲乐曲;
V(music_over);//通知老板乐曲结束
}
}
coend
6.某银行有人民币储蓄业务,由n个储蓄员负责。每个顾客进入银行后先取一个号,并且等着叫号。当一个储蓄人员空闲下来,就叫下一个号。请用P,V操作正确编写储蓄人员和顾客进程的程序。
var customer_counter,mutex:semaphore;
customer_counter:=0;
mutex:=1;
cobegin
process customer
begin
L1:take a number;
P(mutex0);
进入队列;
V(mutex);
V(customer_count);
go to L1;
end
Process serversi(i=1,2,3...)
begin
P(cunstom_count);
P(mutex);
从队列区号;
V(mutex);
为该号客人服务;
end;
coend;
8.九、在一个盒子里,混装了相等数量的黑棋子和白棋子,现要用自动分拣系统把黑棋子和白棋子分开,该系统由两个并发执行的进程P1和P2组成,其中进程P1专门拣黑子,进程P2专门拣白子。规定两个进程轮流拣子且每个进程每次只拣一个子。当一个进程在拣子时不允许另一个进程去拣子,并设P1先拣。请用P,V操作管理这两个并发进程,使其能正确实现上述功能
var S1,S2:semaphore;
S1:=1;S2:=0;
cobegin
{
process P1
begin
repeat
P(S1);
拣白子
V(S2);
until false;
end
process P2
begin
repeat
P(S2);
拣黑子
V(S1);
until false;
end
}
coend.