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 }

执行结果:

原文地址:https://www.cnblogs.com/guanghe/p/15246894.html