天天看點

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操作代表喚醒被阻塞程序的操作。

六、生産者消費者問題

PV操作

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 }      

執行結果:

PV操作