zoukankan      html  css  js  c++  java
  • PV操作

    一、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操作定义如下:

    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修饰的方法是一个最终方法,不能被重写,重写会报错。

      1 import java.util.Scanner;
      2 
      3 public class PV {
      4 
      5     //信号量
      6     static class Semaphore {
      7         public int value;
      8         public Semaphore(int value) {
      9             this.value = value;
     10         }
     11         //P操作(passeren,通过)
     12         public synchronized final void P() {
     13             value--;
     14             if (value < 0) {
     15                 try {
     16                     //当缓冲区已满/空时,生产者/消费者线程停止自己的执行,释放锁,使自己处于等待状态,让其它线程执行
     17                     this.wait();
     18                 } catch (InterruptedException e) {
     19                     e.printStackTrace();
     20                 }
     21             }
     22         }
     23         //V操作(vrijgeven,释放)
     24         public synchronized final void V() {
     25             value++;
     26             if (value <= 0) {
     27                 //当生产者或消费者向缓冲区放入或取出一个产品时,向其他等待的线程发出通知,同时释放锁,使自己处于等待状态,让其它线程执行。
     28                 this.notify();
     29             }
     30         }
     31     }
     32 
     33     static class Global {
     34         //空闲缓冲区初始化为3
     35         public static Semaphore empty = new Semaphore(3);
     36         //满缓冲区初始化为空
     37         public static Semaphore full = new Semaphore(0);
     38         //临界区互斥信号量
     39         public static Semaphore mutex = new Semaphore(1);
     40         //count用于缓冲区中的进程进行计数
     41         public static int count = 0;
     42         //定时等待
     43         public static void timingwait() {
     44             try {
     45                 Thread.sleep(2000);
     46             } catch (InterruptedException e) {
     47                 e.printStackTrace();
     48             }
     49         }
     50     }
     51 
     52     //生产者
     53     class Producer implements Runnable {
     54         @Override
     55         public void run() {
     56             String threadName = Thread.currentThread().getName();
     57             Global.timingwait();
     58             System.out.println(threadName + " 生产出一个商品...");
     59             //获取空缓冲区单元
     60             Global.empty.P();
     61             //进入临界区
     62             Global.mutex.P();
     63             Global.timingwait();
     64             System.out.println(threadName + " 将产品放入缓冲区--缓冲区剩余 " + (++Global.count) + " 个产品");
     65             //离开临界区,释放信号量
     66             Global.mutex.V();
     67             //满缓冲区数加1
     68             Global.full.V();
     69         }
     70     }
     71 
     72     //消费者
     73     class Consumer implements Runnable {
     74         @Override
     75         public void run() {
     76             String threadName = Thread.currentThread().getName();
     77             Global.timingwait();
     78             //获取满缓冲区单元
     79             Global.full.P();
     80             //进入临界区
     81             Global.mutex.P();
     82             Global.timingwait();
     83             System.out.println(threadName + " 从缓冲区取出一个产品--缓冲区剩余 " + (--Global.count) + " 个产品");
     84             //离开临界区,释放互斥信号量
     85             Global.mutex.V();
     86             //空缓冲区加1
     87             Global.empty.V();
     88             System.out.println(threadName + " 消费一个商品...");
     89         }
     90     }
     91 
     92     public static void main(String[] args) {
     93         int producer, consumer;
     94         Scanner sc = new Scanner(System.in);
     95         System.out.print("请输入生产者数目:");
     96         producer = sc.nextInt();
     97         System.out.print("请输入消费者数目:");
     98         consumer = sc.nextInt();
     99         for (int i = 0; i < producer; i++) {
    100             new Thread(new PV().new Producer(), "生产者" + i + "号").start();
    101         }
    102         for (int i = 0; i < consumer; i++) {
    103             new Thread(new PV().new Consumer(), "消费者" + i + "号").start();
    104         }
    105     }
    106 }

    执行结果:

  • 相关阅读:
    LeetCode
    数据流中的中位数
    二叉搜索树的第k个结点
    对称的二叉树
    按之字形顺序打印二叉树
    把二叉树打印成多行
    二叉树的下一个结点
    链表中环的入口结点
    删除链表中重复的结点
    不用加减乘除做加法
  • 原文地址:https://www.cnblogs.com/guanghe/p/15246894.html
Copyright © 2011-2022 走看看