【操作系统】2.6进程管理(经典的进程同步、互斥问题)

1.生产者和消费者问题

  1. 问题描述:
    系统中有一组生产者进程和一组消费者进程,生产者每次生产一个产品放入缓冲区,消费者每次从缓冲区取出一个产品并使用。(注:这里的“产品”理解为某种数据)
    生产者、消费者共享一个初始为空,大小为n的缓冲区
  2. 关系分析:
    1. 只有缓冲区没满时,生产者才能把产品放入缓冲区,否则必须等待(同步关系1:缓冲区满时,生产者要等待消费者取走产品)
    2. 只有缓冲区不空时,消费者才能从中取出产品,否则必须等待(同步关系2:缓冲区空时,消费者要等待生产者放入产品)
    3. 缓冲区是临界资源,各个进程必须互斥地访问(互斥关系)
  3. 思路:(问题关键)
    1. 生产者每次要消耗P一个空闲缓冲区,并生产V一个产品
    2. 消费者每次要消耗P一个产品,并释放V一个空闲缓冲区
    3. 往缓冲区存放/取走产品需要互斥

    如何用信号量机制实现生产者、消费者之间的关系?

    1. 互斥:设置初值为1的互斥信号量
    2. 同步:设置初值为0的同步信号量(实现“一前一后”)
    3. 对一类系统资源的申请和释放:设置一个信号量,初值为资源的数量,(实质也是同步问题,若无资源,则申请进程需要等待别的进程释放资源后才能继续执行)
  4. 设置信号量:
    :有时候生产者要等待消费者,有时候消费者要等待生产者。这是两个不同的一前一后关系,因此要设置两个同步信号量
    //互斥信号量初值为1;同步信号量初值为资源数
    semaphore mutex=1;		//互斥信号量
    semaphore empty=n;		//同步信号量,表示  空缓冲区  的数量
    semaphore full=0;		//同步信号量,表示  非空缓冲区  (产品)的数量
    
  5. 实现:
    //实现互斥:是在同一进程中进行一对PV操作
    //实现两个进程间同步:是在其中一个进程执行P操作,在另一个执行V操作
    producer(){
          
          
    	while(1){
          
          
    		生产一个产品;
    		P(empty);		//消耗了一个空缓冲区
    		P(mutex);		//对资源上锁
    		把产品放入缓冲区;
    		V(mutex);		//对资源解锁
    		V(full);		//产生了一个非空缓冲区
    	}
    }		
    consumer(){
          
          
    	while(1){
          
          
    		P(full);		//消耗了一个非空缓冲区
    		P(mutex);
    		从缓冲区取出产品;
    		V(mutex);
    		V(empty);		//产生了一个空缓冲区
    		使用产品;		
    	}
    }	
    

    思考:
    交换了两个进程中各自的两个P操作的顺序会发生什么?

    	producer(){
           
           
    		while(1){
           
           
    			生产一个产品;
    			P(mutex);		//若是先P互斥信号量
    			P(empty);		//再P资源
    			把产品放入缓冲区;
    			V(mutex);		
    			V(full);		
    		}
    	}		
    	consumer(){
           
           
    		while(1){
           
           
    			P(mutex);		//若是先P互斥信号量
    			P(full);		//再P资源
    			从缓冲区取出产品;
    			V(mutex);
    			V(empty);		
    			使用产品;		
    		}
    	}	
    

    若此时缓冲区已经放满产品,即empty=0,full=n,

    1. 由于生产者先将资源上锁P(mutex),然后申请空的缓冲区P(empty),但此时没有空闲的缓冲区,P之前empty=0,此时empty=-1生产者进程主动阻塞,等待消费者从缓冲区取走产品
    2. 但消费再执行时,首先要检查资源是否上锁P(mutex),发现P之前mutex=0,资源被生产者进程上锁了,此时mutex=-1,则消费者主动阻塞,等待生产者进程解锁临界区,释放临界资源
    3. 这种情况造成了生产者等待消费者释放缓冲区消费者等待生产者释放临界区的情况,双方都在等待对方的唤醒,出现“死锁”
    4. 因此实现互斥的P操作一定要在实现同步的P操作
      但V操作不会引起进程的阻塞,因此==两个V操作的顺序可以交换= =

2.多生产者和多消费者问题

  1. 问题描述:
    桌子上只有一个盘子,每次只能往盘子里放一个水果,父亲放苹果,母亲放橘子;儿子只从盘子里拿橘子吃,女儿只从盘子里拿苹果吃。只有当盘子空时,父母才能往盘子里放水果,只有当盘子放着水果时,儿子女儿才能从盘中拿水果吃
  2. 关系分析:
    1. 这里的产品和之前不同,并不是一类产品,是有多种产品(苹果、橘子),所以这类多生产者和多消费者问题,并不是说有多个生产、消费者,而是有多种生产、消费者****(问题关键)
    2. 互斥关系:对缓冲区(盘子)的访问
    3. 同步关系
      1. 父亲放入苹果后,女儿才能拿走
      2. 母亲放入橘子后,儿子才能拿走
      3. 只有当盘子为空时,父母才能放入水果(盘子为空这个事件可以有儿子或女儿触发,事件发生后才允许父亲或母亲放水果)

      解决多生产者和多消费者的问题关键一点在于理清复杂的同步关系:

      1. 在分析同步问题时,不能从单个进程的行为的角度区分析,而是要从一前以后发生的两个事件的角度去分析
      2. 比如从单个进程的行为的角度区分析,该问题的关系变为:
        如果盘子里装有苹果,那么一定要儿子取走苹果后父亲或母亲才能放入水果
        如果盘子里装有橘子,那么一定要女儿取走橘子后父亲或母亲才能放入水果
        那么同步关系就变为了:
        在这里插入图片描述
        但如果这样的话,就需要设置四个同步信号量来实现者四对同步关系
      3. 正确的分析方法是:
        事件的角度 去分析关系,将上述四对进程行为抽象一对事件的前后关系
        盘子变空事件放入水果事件
        盘子变空事件可由儿子或女儿拿走水果触发
        放水果事件可由父亲或者母亲执行
        这样只需要设置一个同步信号量 plate 就可以实现
        在这里插入图片描述
  3. 设置信号量:
    semaphore mutex=1;		//互斥信号量
    semaphore apple=0;		//同步信号量,表示  盘中苹果  的数量
    semaphore orange=0;		//同步信号量,表示  盘中橘子  的数量
    semaphore plate=1;		//同步信号量,表示  盘子空位  的数量
    
  4. 实现:
    dad(){
          
          
    	while(1){
          
          
    		准备一个苹果;
    		P(plate);		//消耗一个盘中空位
    		P(mutex);		//对盘子上锁
    		把苹果放入盘子;
    		V(mutex);		//对盘子解锁
    		V(apple);		//盘子里apple加一
    	}
    }		
    mom(){
          
          
    	while(1){
          
          
    		准备一个橘子;
    		P(plate);		//消耗一个盘中空位
    		P(mutex);		//对资源上锁
    		把橘子放入盘子;
    		V(mutex);		//对资源解锁
    		V(orange);		//盘子里orange加一
    	}
    }		
    son(){
          
          
    	while(1){
          
          
    		P(apple);		
    		P(mutex);
    		拿走苹果;
    		V(mutex);
    		V(plate);		
    		吃苹果;		
    	}
    }	
    daughter(){
          
          
    	while(1){
          
          
    		P(orange);		
    		P(mutex);
    		拿走橘子;
    		V(mutex);
    		V(plate);		
    		吃橘子;		
    	}
    }	
    
    1. 思考:
      可不可以不用互斥信号量?
    2. 分析:
      1. 如果刚开始,儿子、女儿进程即使上处理机,由于开始时apple=0、orange=0也会被阻塞
      2. 如果刚开始,由父亲进程获得处理机,则父亲P(plate),可以访问盘子→母亲P(plate),阻塞等待盘子→父亲将苹果放入盘子V(apple)唤醒阻塞的儿子进程儿子P(apple),访问盘子取出苹果,V(plate),等待盘子的母亲进程被唤醒母亲P(plate),访问盘子。。。【同步先V后P】
    3. 结论:
      上述分析中得出,即使不使用互斥信号量,也不会出现多个进程同时访问临界资源(盘子)的现象
    4. why?
      本题中的缓冲区大小为1,在任何情况下,apple、orange、plate三个同步信号量最多只有1。因此在任何时刻,最多只有一个进程P操作不会被阻塞,并顺利进入临界区
    5. 如果缓冲区大小为2,即盘子中由两个位置放水果
      plate初值为2,当父亲P(plate)后plate=1,父亲可以访问盘子,但紧接着母亲也P(plate)后plate=0,母亲此时也可以访问盘子,此时若父母两人都想往盘子中的二号位放水果,那么后放的水果(数据)就会 覆盖 掉前面放的水果。因此,如果缓冲区的大小大于1,就必须专门设置一个互斥信号量mutex,来保证互斥地访问缓冲区

3.吸烟者问题

(问题关键) 可以生产多种产品的单生产问题

  1. 问题描述:
    假设系统中有三个抽烟者进程,一个供应者进程。每个抽烟者不断卷烟并抽掉它,但要获得纸、胶水和烟草三种材料。第一个抽烟者手里有烟草,第一个抽烟者手里有纸、第一个抽烟者手里有胶水。供应者进程无限地提供三种材料,供应者每次将两种材料的组合放到桌子上,拥有剩余材料的抽烟者九月拿走这两种材料并抽掉它,并提供一个信号告诉提供者抽烟完成了,提供者就会继续将两外两种材料放到桌子上,这个过程重复(让三个抽烟者重复抽烟)
  2. 关系分析:
    1. 互斥关系:桌子可以抽象为容量为1的缓冲区,不断往其中放三种组合:纸+胶水、纸+烟草和胶水+烟草
    2. 同步关系:(从事件角度分析)
      1. 桌子上有组合1,抽烟者1拿走并卷烟抽掉
      2. 桌子上有组合2,抽烟者2拿走并卷烟抽掉
      3. 桌子上有组合3,抽烟者3拿走并卷烟抽掉
      4. 任何一个抽烟者抽完烟,向提供者发信号,让提供者继续提供材料
  3. 设置信号量:
    semaphore offer1=0;		//桌子上有组合1
    semaphore offer2=0;		//桌子上有组合2
    semaphore offer3=0;		//桌子上有组合3
    semaphore finish=0;		//抽烟是否完成
    int i = 0;				//实现三个抽烟者轮流抽烟
    

    本问题中缓冲区大小为1,故也可以选择不设置互斥信号量

  4. 实现:
    provider(){
          
          
    	while(1){
          
          
    		if(i==0){
          
          			//如果一个生产者要生产多种产品(或会引发多种前驱事件)
    			将组合1放桌上;
    			V(offer1);		//那么各个V操作应该放在各自对应的"事件"发生之后
    		}else if(i==1){
          
          
    			将组合2放桌上;
    			V(offer2);
    		}else if(i==2){
          
          
    			将组合3放桌上;
    			V(offer3);
    		}
    		i = (i+1)%3;		//实现轮流抽烟,若要实现随机抽烟: i= Math.random(),随机数范围为 0.0 =< Math.random < 1.0
    		P(finish);		
    	}
    }		
    
    smoker1(){
          
          
    	while(1){
          
          
    		P(offer1);			//发现桌上有组合1	
    		拿走组合1,并抽烟;		
    		V(finish);			//发出抽烟完成的信号
    	}
    }		
    smoker2(){
          
          
    	while(1){
          
          
    		P(offer2);			
    		拿走组合2,并抽烟;		
    		V(finish);
    	}
    }	
    smoker3(){
          
          
    	while(1){
          
          
    		P(offer3);			
    		拿走组合3,并抽烟;		
    		V(finish);	
    	}
    }	
    

4.读者写者问题

  1. 问题描述:
    有读者和写者两组并发进程,共享一个文档
    1. 当两个或两个以上的读进程同时访问共享数据时不会产生副作用。(与消费者不同,读进程在读数据之后并不会清空数据,因此可以有多个读者同时读数据)
    2. 若某个写进程和读进程同时访问共享数据时则可能导致数据不一致的错误。
    3. 若某个写进程和写进程同时访问共享数据时则可能导致数据覆盖的问题。
    4. 任何一个写者在完成写操作之前不允许其他读者或者写者进行操作
    5. 写进程执行写操作前,应让已有读进程或者写进程全部退出
  2. 关系分析:
    1. 步骤一:
      1. 该问题中只存在互斥关系
        1. 写进程读进程之间存在互斥
        2. 写进程之间存在互斥
        3. 读进程之间不存在互斥 (问题关键)
      2. 实现:设置一个互斥信号量rw,写进程之间要互斥,所以在写操作之前P一下rw,写完后V一下rw。又因为读进程与写进程之间也要互斥,所以在读操作之前也P一下rw,读完后V一下rw。
      3. 问题:这样并不能实现多个读进程同时访问共享数据
      4. ⭐解决办法:设置一个 int变量count 来记录有多少各读进程访问共享数据:判断当前读数据是不是第一个读进程,如果是第一个读进程,则需要PV rw,再count+1,保证与写进程互斥。但如果不是第一个读进程,则表明当前正在有读进程读,再有读进程读的话保证肯定不会和写进程冲突,故不用PV rw,直接让count+1。
    2. 步骤二:
      1. 设置信号量:
        semaphore rw=1;				//用于实现对共享数据的互斥访问
        int count = 0;				//记录当前有几个读进程在访问共享数据
        semaphore contrlCount=1;		//用于保证对count的修改操作是互斥的
        

        思考:为什么要设置 contrlCount ?
        答:如果不设置 contrlCount的话,当读者1,在进行if语句判断后,判断到自己是第一个读者,则会P rw,但再其将count+1之前,另一个读者2进入,也执行if语句,由于读者1还没来得及对count修改,导致读者2也以为自己是第一个读者,也会去P rw,会导致读者2阻塞等待rw
        解决办法: 实现将判断count的操作修改count的操作变为一气呵成的,即设置一个 互斥信号量 contrlCount ,保证对count的所有操作是互斥的

      2. 实现:
        writer(){
                  
                  
        	while(1){
                  
                  
        		P(rw);			//写之前上锁
        		写数据;		
        		V(rw);			//写完解锁
        	}
        }		
        reader(){
                  
                  
        	while(1){
                  
                  
        		P(contrlCount);	//对count变量的操作一定要保证互斥
        		if(count==0){
                  
                  
        			P(rw);		//读之前上锁
        		count++;			
        		V(contrlCount);
        		
        		读数据;
        		
        		P(contrlCount);
        		count--;		
        		if(count==0){
                  
                  
        			V(rw);		//读之后解锁
        		V(contrlCount);
        	}
        }	
        
      3. 存在的问题: 由于后来的读进程不再需要P rw ,若有源源不断的读进程到达,这些读进程会一直可以执行,这样会导致因为P不到rw而阻塞的写进程饿死 ,显然上面的方法是读进程优先的
      4. ⭐解决办法: 之前当一个写进程到达时,后来的读进程不会察觉到在他们之前有写进程达到,而是直接读数据,所以设置一个 互斥信号量w,当一个读进程1在读数据时,写进程到达,将会P w,接制P rw,因为读进程1还没读完,还没有V rw,故写进程会阻塞,接着读进程2达到,先P w,但因为写进程还没有V w,故读进程2也会阻塞,接着若读进程1读完数据,则会V rw,唤醒因为P不到rw的写进程,而此时读进程2仍然P不到w而进行阻塞。当写进程写完数据后,V rw 接着 V w,唤醒因为P步到w而阻塞的读进程二。这样就实现了写进程在读进程2前到达,故也应该在读进程2之前执行,实现了先到先服务的公平
    3. 步骤三:
      1. 设置信号量:
        semaphore rw=1;				
        int count = 0;				
        semaphore contrlCount=1;		
        semaphore w=1;			//改变读优先,实现读写公平	
        
      2. 实现:

        保证写进程可以在比其后到的读进程之前执行,因为 写进程到达后会将w P走,后到的读进程会因为P不到w而阻塞,之前的读进程释放rw后,写进程就会被唤醒执行

        writer(){
                  
                  
        	while(1){
                  
                  
        		P(w);				//保证写进程可以在比其后到的读进程之前执行
        		P(rw);			
        		写数据;		
        		V(rw);			
        		V(w);				//
        	}
        }		
        reader(){
                  
                  
        	while(1){
                  
                  
        		P(w);				//
        		P(contrlCount);	
        		if(count==0){
                  
                  
        			P(rw);		
        		count++;			
        		V(contrlCount);
        		V(w);				//
        		
        		读数据;
        		
        		P(contrlCount);
        		count--;		
        		if(count==0){
                  
                  
        			V(rw);		
        		V(contrlCount);
        	}
        }	
        

5.哲学家进餐问题

  1. 问题描述:
    一张圆桌上坐着5名哲学家,每两个哲学家之间桌上摆一根筷子,桌子中间是一碗米饭。哲学家们用毕生精力用于思考和进餐,哲学家在思考时,并不影响他人。只有当哲学家饥饿时,才试图拿起左、右两根筷子(一根一根拿起)。如果筷子以在他人手中,则需等待。饥饿的哲学家需要同时拿起两根筷子才可以开始进餐,当进餐完毕后,放下筷子继续思考。
  2. 关系分析:
    系统中五个哲学家进程与左右相邻的哲学家对其中间筷子的访问是互斥关系 ,这个问题中只有互斥关系,但与之前的问题不同的是,每个哲学家要同时持有两个临界资源才能开始吃饭。如何避免临界资源分配不当造成的死锁现象 (问题关键)
  3. 设置信号量:
    semaphore chopstick[5]={
          
          1,1,1,1,1};
    
  4. 实现:
    	Pi(){
          
          
    		while(1){
          
          
    			P(chopstick[i]);
    			P(chopstick[(i+1)%5]);			
    			吃饭...;		
    			V(chopstick[i]);
    			V(chopstick[(i+1)%5]);	
    			思考...;	
    		}
    }		
    

    存在的问题: 如果五个哲学家都拿起了自己左手边的筷子,则进入死锁

    • 如何防止死锁的发生?
      1. 方案一: 可以对哲学家进程施加一些限制条件,比如最多允许四个哲学家同时进餐,这样保证至少有一个哲学家是可以拿到左右两只筷子的。(四个人都拿左边的筷子,肯定有一个人右边会剩一只筷子)
      2. 方案二: 可以对哲学家进程施加一些限制条件,比如要求奇数号哲学家先拿左边的筷子,然后再拿右边的筷子,而偶数号哲学家刚好相反。用样如果相邻的奇偶数哲学家想进餐,则会首先争抢他俩中间的那只筷子,故只有一个人可以拿起第一只筷子,另一个哲学家会在手里没有筷子的情况下阻塞,这样避免了占有了一只等待另一只的情况
      3. 方案三: 仅当一个哲学家左右两个筷子都可以拿起时才允许他拿起筷子(更准确的说法是:各个哲学家拿筷子的动作是互斥的,在一个哲学家拿到一只筷子而另一只拿不到而阻塞时或者正在拿筷子的时候,不允许其他的哲学家拿筷子)

        如一个哲学家1,P(mutex)拿到了左右两只筷子,接着V(mutex)吃饭,此时他左边的哲学家2开始拿筷子,他可以拿到哲学家2可以P(mutex),接着可以拿到他左边的筷子,而右边的哲学家1正在使用,此时哲学家2会阻塞等待他右边的筷子被哲学家1吃完释放,而此时由于哲学家2被阻塞所以mutex并不会被V释放,所以其他的哲学家想进餐也因为P不到mutex而无法进行拿筷子的动作

        1. 设置信号量:
          semaphore chopstick[5]={
                      
                      1,1,1,1,1};
          semaphore mutex =1;
          
        2. 实现:
          Pi(){
                      
                      
          	while(1){
                      
                      
          	
          		P(mutex);			//保证各个哲学家拿筷子的动作是互斥的
          		P(chopstick[i]);
          		P(chopstick[(i+1)%5]);			
          		V(mutex);			//
          		
          		吃饭...;		
          		V(chopstick[i]);
          		V(chopstick[(i+1)%5]);	
          		思考...;	
          	}
          }		
          

猜你喜欢

转载自blog.csdn.net/Qmilumilu/article/details/113243082