• PV操作经典问题通解


    PV操作经典问题包括:读者写者问题,生产者消费者问题,哲学家就餐问题,理发师问题。
    下面先给出解题通用步骤,再对每一类问题做例题分析,并给出每类问题的特解。

    一 PV操作思考通用步骤:

    1.有几类进程?每类进程对应一个函数。

    生产者1进程对应producer1()
    消费者1进程对应consumer1()
    消费者2进程对应consumer2()
    2.在函数内部用中文描述动作,并在草纸上行间留白供后续使用,并根据这些动作是否重复进行,决定是否加while(1)

        producer{
            while1{
    
                生产一件商品;
    
                把商品放到货架;
    
            }
        }
    

    3.在每个动作之前,思考是否需要P什么?如果需要P操作,写出P操作,并写出对应的V操作(V操作可能在另一进程内)

        producer{
            while1{
                //不需要P
                生产一件商品;
                //由于需要消耗货架容量,所以需要P(货架空闲区)
                //货架属于临界资源需要互斥访问,所以P(货架互斥信号量)
                把商品放到货架;
                //P的对应V操作:释放临界资源,所以V(货架互斥信号量)
                //由于货架被放上货物,货物量增加,所以V(货架货物量)
           
    

    4.写完PV操作后,再完整地定义信号量

        semaphore mutex=1//临界资源
        semaphore empty=5//空闲区数量
        semaphore full=0//货物数量
        int count=0//整形信号量,用于统计人数等
        semaphore CSignal;//在count++ count————时,套上P()V(),实现互斥更改count值
    

    5.完成上述步骤之后进行检查,检查多个P操作连续出现时是否可能出现死锁。一对PV前后出现,不会死锁;连续多个P导致死锁(请求和保持),可尝试调换前后P的顺序。

    二生产者消费者问题

    例一用于实操一遍通解过程的1,2,3,4,5步,例二用于说明第5步会遇到的问题。

    例一:某工厂有两个生产车间和一个装配车间,两个生产车间分别生产 A、B 两种零件,装配车间的任务是把 A、B 两种零件组装成产品。两个生产车间每生产一个零件后,都要分别把它们送到装配车间的货架 F1、F2上。F1 存放零件 A,F2 存放零件 B,F1 和 F2 的容量均可存放 10 个零件。装配工人每次从货架上取一个零件 A 和 一个零件 B 后组装成产品。请用 P、V 操作进行正确管理。

    思考步骤,解体流程:
    1.有几类进程?每类进程对应一个函数。

    生产者A进程对应producerA()
    生产者B进程对应producerB()
    消费者进程对应consumer()
    2.在函数内部用中文描述动作,并在草纸上行间留白供后续使用,并根据这些动作是否重复进行,决定是否加while(1)

        producerA{
            while1{
    
                生产一件零件A;
    
                把零件A放到货架F1;
    
            }
        }
        producerB{
            while1{
    
                生产一件零件B;
    
                把零件B放到货架F2;
    
            }
        }
        consumer{
            while1{
    
                从货架F1取一件零件A;
                
                从货架F2取一件零件B;
    
                把零件A和B组装;
    
            }
        }
    

    3.在每个动作之前,思考是否需要P什么?如果需要P操作,写出P操作,并写出对应的V操作(V操作可能在另一进程内)

        producerA{
            while1{
                
                生产一件零件A;
                P(emptyF1)//由于需要消耗货架容量,所以需要P(货架空闲区)
                P(mutexF1)//货架属于临界资源需要互斥访问,所以P(货架互斥信号量)
                把零件A放到货架F1;
                V(mutexF1);//P的对应V操作:释放临界资源,所以V(货架互斥信号量)
                V(fullF1)//由于货架被放上货物,货物量增加,所以V(货架货物量)
    
            }
        }
        producerB{
            while1{
    
                生产一件零件B;
                P(emptyF2)//由于需要消耗货架容量,所以需要P(货架空闲区)
                P(mutexF2)//货架属于临界资源需要互斥访问,所以P(货架互斥信号量)
                把零件B放到货架F2;
                V(mutexF2);//P的对应V操作:释放临界资源,所以V(货架互斥信号量)
                V(fullF2)//由于货架被放上货物,货物量增加,所以V(货架货物量)
            }
        }
        consumer{
            while1{
                P(fullF1)//由于需要消耗货物A,所以需要P(货架货物量)
                P(mutexF1)//货架属于临界资源需要互斥访问,所以P(货架互斥信号量)
                从货架F1取一件零件A;
                V(mutexF1);//P的对应V操作:释放临界资源,所以V(货架互斥信号量)
                V(emptyF1)//取完货物,货架F1空闲区增加,所以V(货架空闲区)
                  
                
                P(fullF2)//由于需要消耗货物B,所以需要P(货架货物量)
                P(mutexF2)//货架属于临界资源需要互斥访问,所以P(货架互斥信号量)
                从货架F2取一件零件B;
                V(mutexF2);//P的对应V操作:释放临界资源,所以V(货架互斥信号量)
                V(emptyF2)//取完货物,货架F2空闲区增加,所以V(货架空闲区)
    
                把零件A和B组装;
    
            }
        }
    

    4.写完PV操作后,再完整地定义信号量

        semaphore mutexF1=1//临界资源
        semaphore mutexF2=1;
        semaphore emptyF1=10//空闲区数量
        semaphore emptyF2=10;
        semaphore full1F=0//货物数量
        semaphore full2F=0

    5.完成上述步骤之后进行检查,检查多个P操作连续出现时是否可能出现死锁。一对PV前后出现,不会死锁;连续多个P导致死锁(请求和保持),可尝试调换前后P的顺序。

    未发现死锁现象,完整答案:

        semaphore mutexF1=1//临界资源
        semaphore mutexF2=1;
        semaphore emptyF1=10//空闲区数量
        semaphore emptyF2=10;
        semaphore full1F=0//货物数量
        semaphore full2F=0;
        producerA{
            while1{
                
                生产一件零件A;
                P(emptyF1)//由于需要消耗货架容量,所以需要P(货架空闲区)
                P(mutexF1)//货架属于临界资源需要互斥访问,所以P(货架互斥信号量)
                把零件A放到货架F1;
                V(mutexF1);//P的对应V操作:释放临界资源,所以V(货架互斥信号量)
                V(fullF1)//由于货架被放上货物,货物量增加,所以V(货架货物量)
    
            }
        }
        producerB{
            while1{
    
                生产一件零件B;
                P(emptyF2)//由于需要消耗货架容量,所以需要P(货架空闲区)
                P(mutexF2)//货架属于临界资源需要互斥访问,所以P(货架互斥信号量)
                把零件B放到货架F2;
                V(mutexF2);//P的对应V操作:释放临界资源,所以V(货架互斥信号量)
                V(fullF2)//由于货架被放上货物,货物量增加,所以V(货架货物量)
            }
        }
        consumer{
            while1{
                P(fullF1)//由于需要消耗货物A,所以需要P(货架货物量)
                P(mutexF1)//货架属于临界资源需要互斥访问,所以P(货架互斥信号量)
                从货架F1取一件零件A;
                V(mutexF1);//P的对应V操作:释放临界资源,所以V(货架互斥信号量)
                V(emptyF1)//取完货物,货架F1空闲区增加,所以V(货架空闲区)
                  
                
                P(fullF2)//由于需要消耗货物B,所以需要P(货架货物量)
                P(mutexF2)//货架属于临界资源需要互斥访问,所以P(货架互斥信号量)
                从货架F2取一件零件B;
                V(mutexF2);//P的对应V操作:释放临界资源,所以V(货架互斥信号量)
                V(emptyF2)//取完货物,货架F2空闲区增加,所以V(货架空闲区)
    
                把零件A和B组装;
    
            }
        }
    

    例二:某寺庙有小和尚、老和尚若干,有一水缸,由小和尚提水入缸供老和尚引用。水缸可容 10 桶水,水桶自同一井中。水井径窄,每次只能容一个桶取水。水桶总数为 3 个。每次入缸取水仅为 1 桶水,且不可同时进行。试给出有关从缸取水、入水的算法描述。

    先给出正确解答,再说明第5步可能出现的问题。

    semaphore well = 1;		//用于互斥地访问水井;
    semaphore vat = 1;		//用于互斥的访问水缸;
    semaphore empty = 10;	//用于表示水缸中剩余空间;
    semaphore full = 0;		//表示水缸中水的桶数;
    semaphore pail = 3;		//表示有多少个水桶可以用,初始值为3;
    
    // 老和尚
    while(1){
    	P(full);
    	P(pail);	
    	P(vat);
    	从水缸中打一桶水;
    	V(vat);
    	V(empty);
    	喝水;
    	V(pail);
    }
    
    // 小和尚
    while(1){
    	P(empty);
    	P(pail);
    	P(well);
    	从井中打一桶水;
    	V(well);
    	P(vat);
    	将水倒入水缸中;
    	V(vat);
    	V(full);
    	V(pail);
    }
    
    

    我们在写P操作时,遇到的多个P操作,应该先写哪个P呢?我给出策略1可以把互斥访问临界资源的P(mutex)放在最后,而前面的多个P可能不确定,假如老和尚写成

    	P(pail);//取桶
    	P(full);//确定有水	
    	P(vat);//互斥缸
    	从水缸中打一桶水;
    

    而此时缸中无水,且有3个和尚共取了3个桶,那么小和尚无法取桶去取水,老和尚就永远喝不到水,产生死锁。
    这时我们就应该调换P操作顺序为

    	P(full);
    	P(pail);	
    	P(vat);
    	从水缸中打一桶水;
    

    那么我们就会产生疑问,有没有一种思考方式来直接避免多个P出现死锁呢?下面我给出一种策略2
    如果我们要做两件事,且先做A,再做B,那么我们就应该先确保做B所需资源存在,再确定做A所需资源存在。

    比如老和尚要喝水,两件事:先取桶,再从缸里取水,那么我们就应该先确定缸里水存在,再确定桶存在。用PV描述:

    	P(full);确定缸里水存在
    	P(pail);确定桶存在
    

    再比如小和尚要取水,两件事:先从用桶从井里取水,再把水倒入缸中空闲区,那么我们就应该先确定缸中空闲区存在,再确定桶存在。用PV描述:

    	P(empty);
    	P(pail);
    

    三读者写者问题

    对读者优先,读写公平,写者优先三种策略,我们按照通解写出答案,再对特殊部分进行解读。

    三.1读者优先

    按照通解得到:

    semaphore RCsignal=1;//读者数修改互斥
    semaphore mutex=1;//临界资源互斥
    int count=0;//读者数
    
    读者部分:
    reader(){
        while(1){
            P(mutex);;
            V(mutex);
        }
    }
    
    写者部分:
    writer(){
        while(1){
            P(mutex);;
            V(mutex);
        }
    }
    
    

    使用了整形信号量readcount,目的是在第一个读者进入临界资源时,锁住临界资源,使后续读者可以继续访问而后续写者无法访问;在最后一个读者撤出临界资源时,解锁临界资源,使后续读者或写者都可访问。

    semaphore RCsignal=1;//读者数修改互斥
    semaphore mutex=1;//临界资源互斥
    int count=0;//读者数
    
    读者部分:
    reader(){
        while(1){
            P(RCsignal);
            if(count==0)
                P(mutex);
            V(RCsignal);;
            P(RCsignal);
            count--;
            if(count==0)
                V(mutex);
            V(RCsignal);
        }
    }
    
    写者部分:
    writer(){
        while(1){
            P(mutex);;
            V(mutex);
        }
    }
    
    

    三.2读写公平

    读写公平分为两种,一种是部分自行抢占部分先来先服务,一种是先来先服务。
    两者的PV操作区别是写者中P(rw)的位置不同。
    书中,博客中所给的基本都是第一种读写公平,个人认为第二种是更公平的写法。
    部分自行抢占,部分先来先服务:写者A在访问临界资源时,又先后来了B,C,那么B,C有同等概率先访问临界资源;读者A在访问临界资源时,又
    先后来了B,C,那么B将优先于C先访问临界资源。
    部分自行抢占部分先来先服务的具体机制

            部分自行抢占部分先来先服务{
                目前临界资源正被某读者A访问{
                    若此时先后来到一个读者B,一个写者C{
                        B与A同时读;
                    }
                    若此时先后来到一个写者B,一个读者C{
                        A出后,B写;
                    }
                目前临界资源正被某写者A访问{
                    若此时先后来到一个读者B,一个写者C{
                        A出后,BC有同等概率抢占临界资源;
                    }
                    若此时先后来到一个写者B,一个读者C{
                        A出后,BC有同等概率抢占临界资源;
                    }
            }
    

    如何实现这个机制呢?
    我们引入一个信号量rw,当A在读时,来B,B即获得下一位进入临界区的资格。所以B(无论时读写)应首先P(rw),获得资格,此时若再来C,则C被P(rw)阻塞,即C无法获得资格。
    若B为写,已经获得资格,那么B什么时候再释放这个资格呢?答案是当B写完,B写完后,后续来者自行抢占。
    若B为读,已经获得资格,那么B什么时候再释放这个资格呢?答案是当B在读之前,因为读时B已经像A那样允许下一位获得资格。

    semaphore mutex=1;
    semaphore RCsignal=1;
    semaphore rw=1;
    int count=0;
    
    读者部分:
    reader(){
        while(1){
            P(rw);
            P(RCsignal);
            if(count==0)
                P(mutex);
            count++;
            V(RCsignal);
            V(rw);;
            P(RCsignal);
            count--;
            if(count==0)
                V(mutex);
            V(RCsignal);
        }
    }
    
    写者部分:
    writer(){
        while(1){
            P(rw);
            P(mutex);;
            V(mutex);
            V(rw);
        }
    }
    
    

    先来先服务:简单来说A在访问临界资源时,又先后来了B,C,那么B将优先于C先访问临界资源。
    先来先服务的具体机制:

            先来先服务{
                目前临界资源正被某读者A访问{
                    若此时先后来到一个读者B,一个写者C{
                        B与A同时读;
                    }
                    若此时先后来到一个写者B,一个读者C{
                        A出后,B写;
                    }
                目前临界资源正被某写者A访问{
                    若此时先后来到一个读者B,一个写者C{
                        A出后,B读;
                    }
                    若此时先后来到一个写者B,一个读者C{
                        A出后,B写;
                    }
            }
    

    如何实现这个机制呢?
    我们引入一个信号量rw,当A在读或写时,来B,B即获得下一位进入临界区的资格。所以B(无论时读写)应首先P(rw),获得资格,此时若再来C,则C被P(rw)阻塞,即C无法获得资格。
    B已经获得资格,那么B什么时候再释放这个资格呢?答案是当B在读写之前,因为读写时B已经像A那样允许下一位获得资格。

    semaphore mutex=1;
    semaphore RCsignal=1;
    semaphore rw=1;
    int count=0;
    
    读者部分:
    reader(){
        while(1){
            P(rw);
            P(RCsignal);
            if(count==0)
                P(mutex);
            count++;
            V(RCsignal);
            V(rw);;
            P(RCsignal);
            count--;
            if(count==0)
                V(mutex);
            V(RCsignal);
        }
    }
    
    写者部分:
    writer(){
        while(1){
            P(rw);
            P(mutex);
            V(rw);;
            V(mutex);
        }
    }
    
    

    三.3写者优先

    写者优先的规则:
    1.读者与写者,写者与写者不能同时访问缓冲区;
    2.无写进程时,各读者可同时访问缓冲区;
    3.读者和写者都等待时,写者优先访问缓冲区;

    “读者和写者都等待时,写者优先访问缓冲区”解读
    读者写者都等待的情况,即有写者正在访问临界资源,而不可能是有读者正在访问临界资源,这是因为有读者在访问临界资源时,后到的写者 只能等待访问临界资源的读者撤出后 才能访问临界资源(读者写者问题的基本原则),而后到的读者可以与前面的读者共同访问临界资源。

    写优先代码解读
    在读者写者都等待的情况,即有写者正在访问临界资源时,write=0(代码中写了,当访问临界资源的写者撤出时,Wcount=0,才会V(write),使write=0),假如此时按时间顺序又来了读者A,写者B。此时在write=0的情况下,按时间顺序读者A先执行P(write),即读者A被阻塞;然后按时间顺序写者B执行,由于此时Wcount=1,即P(write)语句不会执行,即不会在此阻塞,继续执行直到P(mutex)被阻塞。目前读者A与写者B都处于阻塞状态。但一旦正在访问临界资源的写者退出并执行V(mutex)后,使得mutex=1,并且写者B得知mutex=1,并执行P(mutex),进而进行“写”。A退出时,Wcount=1,因此不会V(write),因此读者B将继续被阻塞。
    此时即实现了,在读者写者都等待时,即有写者正在访问临界资源的情况下,按时间顺序又来了读者A,一个写者B,而执行结果就是,读者A被阻塞,写者B也被阻塞。但一旦正在访问临界资源的写者退出并执行V(mutex)后,写者B进行“写”,即写者B先于读者A,实现了插队。即读者和写者都等待时,写者优先访问缓冲区。

    semaphore write=1;//进程优先互斥
    semaphore mutex=1;//临界资源互斥
    semaphore RCsignal=1;//读者数Rcount修改互斥
    semaphore WCsignal=1;//写者数Wcount修改互斥
    int Rcount=0;//读者数
    int Wcount=0;//写者数
    
    读者部分:
    reader(){
        while(1){
            P(write);
            P(RCsignal);
            if(Rcount==0)
                P(mutex);
            Rcount++;
            V(RCsignal);
            V(write);;
            P(RCsignal);
            Rcount--;
            if(Rcount==0)
                V(mutex);
            V(RCsignal);
        }
    }
    
    写者部分:
    writer(){
        while(1){
            P(WCsignal);
            if(WCsignal==0)
                P(write);
            Wcount++;
            V(WCsignal);
            P(mutex);;
            V(mutex);
            P(WCsignal);
            Wcount--;
            if(Wcount==0)
                V(write);
            V(WCsignal);
        }
    }
    
    

    四哲学家进餐问题

    哲学家进餐问题的特点是只有一类进程,且进程数量小于资源数量。
    解决方法是当一个进程能够得到所有需要的资源时,再一次性取走这些资源。
    通解:

        process(){
            while(1){
                P(mutex);//用于互斥访问资源    
                if(资源A>=a&&资源B>=b){//如果所有资源都足够
                    资源A--//一次性获得全部资源
                    资源B--V(mutex);
                    break;//已经获得资源,跳出循环,至做事
                  }    
                else
                    V(mutex); //资源不够,重复查询
            }
            做事;//比如哲学家吃饭
            P(mutex);//互斥访问资源
            资源A++//一次性退还全部资源
            资源B++V(mutex); 
        }
    

    五理发师问题

    实在复杂,这里跳向一篇好文。
    链接: https://blog.csdn.net/duanzhengbing/article/details/52141699

  • 相关阅读:
    如何制作传统节日网站(纯HTML代码)
    【Jlink & C#】通过C#实现Jlink RTT上位机的功能
    C语言练习之递归实现n的k次方
    信息技术服务连续性策略
    基于java web的网上招标系统
    Item-Based Recommendations with Hadoop
    PE结构学习(2)_PE结构的组成
    cesium 自定义气泡 类
    华为云云耀云服务器L实例评测|部署项目管理工具 Focalboard
    C#:实现区域生长算法(附完整源码)
  • 原文地址:https://blog.csdn.net/likang500/article/details/127111047