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

你可能感兴趣的:(操作系统,c++)