一、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 }
執行結果: