P V 操作

一、PV操作

  PV操作是一种实现进程互斥与同步的有效方法。PV操作与信号量的处理相关。P(passeren)通过,理解为申请资源,V(vrijgeven)释放,理解为释放资源。

  PV操作是典型的同步机制之一。用一个信号量与一个消息联系起来,当信号量的值为0时,表示期望的消息尚未产生;当信号量的值非0时,表示期望的消息已经存在。用PV操作实现进程同步时,调用P操作测试消息是否到达,调用V操作发送消息。

  PV操作,原来这是狄克斯特拉用荷兰文定义的,因为在荷兰文中,通过叫passeren,释放叫vrijgeven,PV操作因此得名。这是在计算机术语中不是用英语表达的极少数的例子之一。

二、信号量

  信号量(semaphore)的概念和PV操作是荷兰科学家E.W.Dijkstra提出来的。信号是铁路交通管理中的一种常用设备,交通管理人员利用信号颜色的变化来实现交通管理。在操作系统中,信号量S是一整数。S大于或等于零,代表可供并发进程使用的资源实体数;在S小于零时,ISl表示正在等待使用资源实体的进程数。建立一个信号量必须说明此信号量所代表的意义并且赋初值。除赋初值外,信号量仅能通过PV操作来访问。
  信号量按其用途可分为两种:

  ①公用信号量。联系一组并发进程,相关的进程均可在此信号量上执行P操作和V操作,初值常常为1,用于实现进程互斥。

  ②私有信号量。联系一组并发进程,仅允许拥有此信号量的进程执行P操作,而其他相关进程可在其上施行V操作。初值常常为0或正整数,多用于实现进程同步。

  PV操作是由两个操作,即P操作和V操作组成的。P操作和V操作是两个在信号量上进行操作的过程,假定用S表示信号量,则把这两个过程记作P(S)和V(S)。

三、PV操作原理

  用PV操作来管理共享资源时,首先要确保PV操作自身执行的正确性。由于P(S)和V(S)都是在同一个信号量S上操作,为了使得它们在执行时不发生因交叉访问信号量S而可能出现的错误,约定P(S)和V(S)必须是两个不可被中断的过程,即让它们在屏蔽中断下执行。把不可被中断的过程称为原语。于是,P操作和V操作实际上应该是P操作原语和V操作原语。

  P操作的主要动作是:

  ①S减1;

  ②若S减1后仍大于或等于0,则进程继续执行;

  ③若S减1后小于0,则该进程被阻塞后放入等待该信号量的等待队列中,然后转进程调度。

  V操作的主要动作是:

  ①S加1;

  ②若相加后结果大于0,则进程继续执行;

  ③若相加后结果小于或等于0,则从该信号的等待队列中释放一个等待进程,然后再返回原进程继续执行或转进程调度。

  PV操作对于每一个进程来说,都只能进行一次,而且必须成对使用。在PV原语执行期间不允许有中断发生。原语不能被中断执行,因为原语对变量的操作过程如果被打断,可能会去运行另一个对同一变量的操作过程,从而出现临界段问题。如果能够找到一种解决临界段问题的元方法,就可以实现对共享变量操作的原子性。

四、进程的同步与互斥

1,进程同步

(1)调用P操作测试消息是否到达

  任何进程调用P操作可测试到自己所期望的消息是否已经到达。若消息尚未产生,则S=0,调用P(s)后,P(S)一定让调用者成为等待信号量S的状态,即调用者此时必定等待直到消息到达;若消息已经存在,则S≠0,调用P(S)后,进程不会成为等待状态而可继续执行,即进程测试到自己期望的消息已经存在。

(2)调用V操作发送消息

  任何进程要向其他进程发送消息时可调用V操作。若调用V操作之前S=0,表示消息尚未产生且无等待消息的进程,则调用V(S)后,V(s)执行S:=S+1使S≠0,即意味着消息已存在;若调用V操作之前S<0,表示消息未产生前已有进程在等待消息,则调用V(S)后将释放一个等待消息者,即表示该进程等待的消息已经到达,可以继续执行。

  在用PV操作实现同步时,一定要根据具体的问题来定义信号量和调用P操作或V操作。一个信号量与一个消息联系在一起,当有多个消息时必须定义多个信号量;测试不同的消息是否到达或发送不同的消息时,应对不同的信号量调用P操作或V操作。

2,进程互斥

(1)设立一个互斥信号量S,表示临界区,其取值为1,0,-1,…其中,S=1表示无并发进程进入S临界区;S=0表示已有一个并发进程进入了S临界区;S等于负数表示已有一个并发进程进入S临界区,且有|S|个进程等待进入S临界区,S的初值为1。

(2)用PV操作表示对S临界区的申请和释放。在进入临界区之前,通过P操作进行申请,在退出临界区之后,通过V操作释放。

五、相关推论

推论1:若信号量S为正值,则该值等于在阻塞进程之前对信号量S可施行的P操作数,亦即等于S所代表的实际还可以使用的物理资源数。

推论2:若信号量s为负值,则其绝对值等于登记排列在该信号量S等待队列之中的进程个数,亦即恰好等于对信号量S实施P操作而被阻塞并进入信号量S等待队列的进程数。

推论3:通常,P操作意味着请求一个资源,V操作意味着释放一个资源。在一定条件下,P操作代表阻塞进程操作,而V操作代表唤醒被阻塞进程的操作。

六、生产者消费者问题

P V 操作

P操作定义如下:

1.mutex减1。

2.若mutex>=0,则P操作返回,该线程可以”通过“并继续执行。

3.若mutex<0,则该线程被阻塞,进入操作系统的阻塞队列。

V操作定义如下:

1.mutex加1。

2.若mutex>0,则V操作返回,该线程继续执行。

3.若mutex<=0,则从阻塞队列中唤醒一个阻塞在该信号量上的线程,然后再返回原线程(调用V操作的线程)继续执行。

核心思想:

1、该类问题需要注意对缓冲区大小为n的处理,当缓冲区中有空时,便可对 empty 变量执行 P 操作,一旦取走一个资源便可执行 V 操作以释放空闲区。对 empty 和 full 变量的 P 操作 必须放在 mutex 的P操作之前。

2、P操作即wait操作表示进程请求一个资源,V操作即signal表示进程释放一个资源,且信号量机制遵循了同步机制的“让权等待”原则。

3、wait():当缓冲区已满/空时,生产者或消费者线程停止自己的执行,释放锁,使自己处于等待状态,让其它线程执行。notify():当生产者或消费者向缓冲区放入或取出一个产品时,向其他等待的线程发出通知,同时释放锁,使自己处于等待状态,让其它线程执行。wait()、nofity()这两个方法必须有锁对象调用,而任意对象都可以作为 synchronized 的同步锁。

4、使用synchronized修饰的方法,叫做同步方法,保证A线程执行该方法的时,其他线程只能在方法外等待。被final修饰的方法是一个最终方法,不能被重写,重写会报错。

import java.util.Scanner;

public class PV {

    //信号量
    static class Semaphore {
        public int value;
        public Semaphore(int value) {
            this.value = value;
        }
        //P操作(passeren,通过)
        public synchronized final void P() {
            value--;
            if (value < 0) {
                try {
                    //当缓冲区已满/空时,生产者/消费者线程停止自己的执行,释放锁,使自己处于等待状态,让其它线程执行
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        //V操作(vrijgeven,释放)
        public synchronized final void V() {
            value++;
            if (value <= 0) {
                //当生产者或消费者向缓冲区放入或取出一个产品时,向其他等待的线程发出通知,同时释放锁,使自己处于等待状态,让其它线程执行。
                this.notify();
            }
        }
    }

    static class Global {
        //空闲缓冲区初始化为3
        public static Semaphore empty = new Semaphore(3);
        //满缓冲区初始化为空
        public static Semaphore full = new Semaphore(0);
        //临界区互斥信号量
        public static Semaphore mutex = new Semaphore(1);
        //count用于缓冲区中的进程进行计数
        public static int count = 0;
        //定时等待
        public static void timingwait() {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    //生产者
    class Producer implements Runnable {
        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            Global.timingwait();
            System.out.println(threadName + " 生产出一个商品...");
            //获取空缓冲区单元
            Global.empty.P();
            //进入临界区
            Global.mutex.P();
            Global.timingwait();
            System.out.println(threadName + " 将产品放入缓冲区--缓冲区剩余 " + (++Global.count) + " 个产品");
            //离开临界区,释放信号量
            Global.mutex.V();
            //满缓冲区数加1
            Global.full.V();
        }
    }

    //消费者
    class Consumer implements Runnable {
        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            Global.timingwait();
            //获取满缓冲区单元
            Global.full.P();
            //进入临界区
            Global.mutex.P();
            Global.timingwait();
            System.out.println(threadName + " 从缓冲区取出一个产品--缓冲区剩余 " + (--Global.count) + " 个产品");
            //离开临界区,释放互斥信号量
            Global.mutex.V();
            //空缓冲区加1
            Global.empty.V();
            System.out.println(threadName + " 消费一个商品...");
        }
    }

    public static void main(String[] args) {
        int producer, consumer;
        Scanner sc = new Scanner(System.in);
        System.out.print("请输入生产者数目:");
        producer = sc.nextInt();
        System.out.print("请输入消费者数目:");
        consumer = sc.nextInt();
        for (int i = 0; i < producer; i++) {
            new Thread(new PV().new Producer(), "生产者" + i + "号").start();
        }
        for (int i = 0; i < consumer; i++) {
            new Thread(new PV().new Consumer(), "消费者" + i + "号").start();
        }
    }
}

执行结果:

P V 操作

上一篇:jquery获取浏览器客户端ip


下一篇:kubernetes PV存储卷类型