计算机操作系统——经典同步问题

一、生产者—消费者问题

1、原型

问题描述:

  • 一组生产者进程和一组消费者进程共享一个初始为空、大小为 n 的缓冲区,只有缓冲区没满时,生产者才把消息放入缓冲区,否则必须等待;只有缓冲区不空时,消费者才能从中读取消息,否则必须等待。由于缓冲区是临界资源,它只允许一个生产者放入消息,或一个消费者从中取出消息。

分析:

1)关系分析:
  • 生产者和消费者对缓冲区的访问属于互斥关系,而针对“消息”则生产者和消费者属于协作关系,只有生产者生产了消息,消费者才能使用消息,因此又是同步关系。
2)思路整理:
  • 两组进程存在互斥和同步关系,也就是要解决互斥和同步PV的操作的位置。
3)信号量设置:
  • 设置一个 mutex互斥信号量,用于控制互斥访问缓冲池,初值设为 1;信号量 full 用于记录当前缓冲池中的“满”缓冲区数,初值为 0;信号量 empty 用于记录当前缓冲池中“空”的缓冲区数,初值为 n;
  • 所以生产者-消费者进程描述可如下:
    	seamphore mutex=1;    //临界区互斥信号量
    	seamphore empty=n;  //空闲缓冲区
    	seamphore full=0;    // 缓冲区初始化为空
    	producer(){                // 生产者进程
    		while(1){
    			produce an item in nextp;   // 生产数据
    			P(empty);(用什么,p一下)// 获取空缓冲区单元
    			P(mutex);(互斥夹紧)  // 进入临界区
    			add nextp to buffer;  (行为)//将数据放入缓冲区
    			V(mutex);(互斥夹紧)  // 离开临界区,释放互斥信号量
    			V(full);(提供什么,V一下)//满缓冲区数加1
    		}
    	}
    	consumer(){      //消费者进程
    		while(1){
    			P(full);    //获取满缓冲区单元
    			P(mutex);  //进入临界区
    			remove an item from buffer;  // 从缓冲区取出数据
    			V(mutex);    // 离开临界区,释放互斥信号量
    			V(empty);    // 空缓冲区数加 1
    			consume the item;   //消费数据
    		}
    	}
    
  • 加锁信号量的顺序不能打乱,否则容易出现死锁。例如 生产者若先执行 P(mutex),再P(empty),消费者先执行P(mutex),再P(full)时,当消费者已将缓冲区放满,而消费者并没有取走产品,即empty=0,当下次仍是生产者进程运行时,由于先执行P(mutex)再P(empty),则会发送阻塞,希望消费者取出产品将其唤醒;轮到消费者进程时,同样先执行P(mutex),但是由于生产者已经封锁mutex信号量,消费者进程因此也阻塞,于是生产者和消费者都会陷入无休止的等待。同理,若消费者将缓冲区已经取空,即负利率=0,下次若还是消费进程执行,那么也会产生类似的死锁。释放信号量的顺序则没有严格要求

2、改进版

问题描述:

  • 桌子上有一个盘子,每次只能向其中放入一个水果。爸爸专向盘子中放苹果,妈妈专向盘子中放橘子,儿子专吃盘子中的橘子,女儿专等吃盘子中的苹果。只有盘子为空时,爸爸或妈妈才可以向盘子中放一个水果;仅当盘子中有自己需要的水果时,儿子或女儿可以从盘子中取出。

分析:

1)关系分析:
  • 爸爸和妈妈是互斥关系,爸爸和女儿、妈妈和儿子是同步关系,而且这两对进程必须连起来,儿子和女儿之间没有互斥和同步关系,因为他们是选择条件执行。
2)思路整理
  • 这里一共由4个进程,可抽象为两个生产者和两个消费者被连接到大小为1的缓冲区上。
3)信号设置
  • 信号量 plate 表示互斥信号量,用于确定是否可以往盘子中放水果,初值为 1 表示允许放入一个;信号量 apple 表示盘中是否还有苹果,初值为 0表示没有不许取;orange 表示盘中是否有橘子,初值同样为 0,orange=1 表示盘子中由橘子允许取。
  • 则用伪程序表示如下:
    	semapore plate=1,apple=0,orange=0;
    	dad(){
    		while(1){
    			prepare an apple;
    			P(plate);   //互斥向盘中取、放水果
    			put the apple on the plate;  //向盘中放苹果
    			V(apple);   // 允许取苹果
    		}
    	}
    	mom(){
    		while(1){
    			prepare an orange;
    			P(plate);
    			put the orange on the plate;
    			V(orange);
    		}
    	}
    	son(){
    		while(1){
    			P(orange);  //互斥从盘中取橘子
    			take an orange from the plate;
    			V(plate);  //允许向盘中放、取水果
    			eat the orange;
    		}
    	}
    	daughter(){
    		while(1){
    			P(apple);
    			take an aplle from the plate;
    			V(plate);
    			eat the apple;
    		}
    	}
    

二、读者-写者问题

问题描述:

  • 有读者和写者两组并发进程,共享一个文件,当两个或两个以上的读进程同时访问共享数据时不会产生副作用,但若某个写进程和其他进程同时访问共享数据时则可能导致数据不一致的错误,因此:
    ① 允许多个读者可以同时对文件执行读操作;
    ② 只允许一个写者往文件中写信息;
    ③ 任一写者在完成写操作之前不允许其他读者进程或写者工作;
    ④ 写者执行写操作前,应让已有的读者和写者全部退出。

问题分析:

  • 1)关系分析,读者和写者互斥的,写者和写者互斥,读者和读者之间不互斥。
  • 2)思路整理:两个进程,读者和写者。由于写者和其他进程都互斥,因此可用互斥信号量的P操作、V操作解决;读者则较为复杂,与写者互斥的同时,又要与其他读者同步,需要一个计数器用于判断当前是否有读者读文件:当有读者时,写者不能写文件,此时读者一直占用文件直到退出,写者才可以写文件;同时,不同读者对计数器的访问也是互斥的。
  • 3)信号量设置:设置 count 信号量为计数器,初值为 0;mutex 为互斥信号量,用于保护更新 count 变量时的互斥;互斥信号量 rw 用于保证读者和写者互斥访问。
①有瑕疵的算法
  • 伪代码如下:
    	int count=0;
    	semaphore mutex=1;
    	semaphore rw=1;
    	writer(){
    		while(1){
    			P(rw);   // 互斥访问共享文件
    			writing
    			V(rw); // 释放共享文件
    		}
    	}
    	reader(){
    		while(1){
    			P(mutex);   // 互斥访问 count 变量
    			if(count==0)  // 当第一个读进程读共享文件时
    				P(rw)  // 阻止写进程
    			count++;
    			V(mutex);   // 释放互斥变量 count
    			reading;
    			P(mutex); 
    			count--;
    			if(count==0)   // 当最后一个读进程读完共享文件
    				V(rw);  // 允许写进程写
    			V(mutex);
    		}
    	}
    
  • 此种方式下,可能导致写进程长时间等待甚至出现“饿死”的情况改变上面这种读进程优先,让写进程优先,需要再增加一个信号量,并在上面的 writer() 和 reader() 函数中各增加一对PV操作,如下:
② 完善的算法
```
	int count=0;
	semaphore mutex=1;
	semaphore rw=1;
	semaphore w=1; // 实现写者优先
	writer(){
		while(1){
			P(w);  // 在无写进程请求时进入
			P(rw);   // 互斥访问共享文件
			writing
			V(rw); // 释放共享文件
			V(w);   // 恢复对共享文件的访问
		}
	}
	reader(){
		while(1){
			P(w);  
			P(mutex);   // 互斥访问 count 变量
			if(count==0)  // 当第一个读进程读共享文件时
				P(rw)  // 阻止写进程
			count++;
			V(mutex);   // 释放互斥变量 count
			V(w);
			reading;
			P(mutex); 
			count--;
			if(count==0)   // 当最后一个读进程读完共享文件
				V(rw);  // 允许写进程写
			V(mutex);
		}
	}
```
  • 此种算法又叫做读写公平法

三、哲学家进餐问题

问题描述:

  • 一张圆桌上坐着5名哲学家,每两名哲学家之间的桌子上摆着一根筷子,两根筷子之间是一碗米饭。哲学家倾注毕生精力于思考和进餐,哲学家思考时不影响其他人。只有当哲学家饥饿时,才试图拿起左、右两根筷子——一根一根地拿起。若筷子已在他人手上,则需要等待。饥饿地哲学家只有同时拿到了两根筷子才能开始进餐,进餐完毕,放下筷子继续思考。

问题分析:

  • 1)5 名哲学家与左右邻座对其中间的筷子的访问时互斥关系。
  • 2)思路整理:显而易见,5 个哲学家对应5 个进程,问题解决的关键就是如何让一名哲学家拿到左右两根筷子而不造成死锁或饥饿现象。解决方法有两个:一是让他们同时拿两根筷子;二是对每名哲学家的动作制定规则,避免饥饿或死锁现象的发生。
  • 3)信号量设置:互斥信号量数组 chopstick[5]={1,1,1,1,1},用于对 5 个筷子的互斥访问;哲学家编号顺序:0~4,哲学家 I 左边筷子的编号为 i,哲学家右边筷子的编号为(i+1)%5。
① 有缺陷算法:
```
	semaphore chopstick[5]={1,1,1,1,1};
	Pi(){
		do{
			P(chopstick[i]);  //取左边筷子
			P(chopstick[(i+1)%5]);// 取右边筷子
			eat;
			V(chopstick[i]);  //放回左边筷子
			V(chopstick[(i+1)%5]);// 放回右边筷子
			think;
		}while(1);
	}
```
  • 此算法存在的问题就是,当5名哲学家都想要进餐并分别拿起左边的筷子时,所有的筷子将被拿光,等到他们再想拿起右边的筷子时,就会发生全被阻塞,出现死锁。若要避免此种情况,可以增加限制条件,如至多允许4名哲学家同时进餐;仅当一名哲学家左右两边筷子都可以用时,才允许他抓起筷子;对哲学家顺序编号,奇数号哲学家先拿起左边筷子,然后拿起右边的,而偶数哲学家相反。
②完善的算法
```
	semaphore chopstick[5]={1,1,1,1,1};
	semaphore mutex=1;
	Pi(){
		do{
			P(mutex);  // 在取筷子前获得互斥量
			P(chopstick[i]);  //取左边筷子
			P(chopstick[(i+1)%5]);// 取右边筷子
			V(mutex);  // 释放取筷子的信号量
			eat;
			V(chopstick[i]);  //放回左边筷子
			V(chopstick[(i+1)%5]);// 放回右边筷子
			think;
		}while(1);
	}
```

四、吸烟者问题

问题描述:

  • 假设一个系统有三个抽烟者进程和一个供应者进程。每个抽烟者不停地卷烟并抽掉它,但要卷起一支烟,抽烟者需要三种材料:烟草、纸和胶水。三个抽烟者中,第一个拥有烟草,第二个拥有纸,第三个拥有胶水。供应者进程无限地提供三种材料,供应者每次将两种材料放到桌子上,拥有剩下那种材料的抽烟者卷一根烟并抽掉它,并给供应者一个信号告诉已完成,此时供应者就会将另外两种材料放到桌子上,循环反复如此。

问题分析:

  • 1)关系分析:供应者与三个抽烟者分别是同步关系。由于供应者无法同时满足两个或以上的抽烟者,三个抽烟者对抽烟这个动作互斥。
  • 2)思路整理:显然有4个进程,供应者作为生产者向三个抽烟者提供材料。
  • 3)信号量设置:信号量 offer1,offer2,offer3 分别表示烟草和纸组合的资源、烟草和胶水组合的资源、纸和胶水组合的资源,信号量 finish 用于互斥进行抽烟动作。
  • 伪代码入戏:
    	int random;  // 存储随机数
    	semaphore offer1=0;
    	semaphore offer2=0;
    	semaphore offer3=0;
    	semaphore finish=0;
    	process P1(){
    		while(1){
    			random=a random num;
    			random=random%3;
    			if(random==0)
    				V(offer1);  // 提供烟草和纸
    			else if(random==1)
    				V(offer2);  // 提供烟草和胶水
    			else
    				V(offer3); //提供纸和胶水
    			put on ;  // 将材料放在桌子上
    			P(finish);
    		}
    	}
    	process P2(){
    		while(1){
    			P(offer3);
    			working;  // 拿起纸和胶水,卷成烟,抽掉
    			V(finish);
    		}
    	}
    	process P3(){
    		while(1){
    			P(offer2);
    			working;  // 拿起烟草和胶水,卷成烟,抽掉
    			V(finish);
    		}
    	}
    	process P4(){
    		while(1){
    			P(offer1);
    			working;  // 拿起纸和烟草,卷成烟,抽掉
    			V(finish);
    		}
    	}
    

上一篇
下一篇

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

御承扬

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

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

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

打赏作者

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

抵扣说明:

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

余额充值