看完了進程同步與互斥機制,我終於徹底理解了 PV 操作
1. 什麼是進程同步
在多道批處理系統中,多個進程是可以並發執行的,但由於系統的資源有限,進程的執行不是一貫到底的, 而是走走停停,以不可預知的速度向前推進,這就是進程的非同步性。
那麼,進程的非同步性會帶來什麼問題呢?舉個例子,如果有 A、B 兩個進程分別負責讀和寫數據的操作,這兩個執行緒是相互合作、相互依賴的。那麼寫數據應該發生在讀數據之前。而實際上,由於非同步性的存在,可能會發生先讀後寫的情況,而此時由於緩衝區還沒有被寫入數據,讀進程 A 沒有數據可讀,因此讀進程 A 被阻塞。
進程同步(synchronization)就是用來解決這個問題的。從上面的例子我們能看出,一個進程的執行可能影響到另一個進程的執行,所謂進程同步就是指協調這些完成某個共同任務的並發執行緒,在某些位置上指定執行緒的先後執行次序、傳遞訊號或消息。
再舉個生活中的進程同步的例子,你想要喝熱水,於是你打了一壺水開始燒,在這壺水燒開之前,你只能一直等著,水燒開之後水壺自然會發生響聲提醒你來喝水,於是你就可以喝水了。就是說水燒開這個事情必須發生在你喝水之前。
注意不要把進程同步和進程調度搞混了:
-
進程調度是為了最大程度的利用 CPU 資源,選用合適的演算法調度就緒隊列中的進程。
-
進程同步是為了協調一些進程以完成某個任務,比如讀和寫,你肯定先寫後讀,不能先讀後寫吧,這就是進程同步做的事情了,指定這些進程的先後執行次序使得某個任務能夠順利完成。
2. 什麼是進程互斥
同樣的,也是因為進程的並發性,並發執行的執行緒不可避免地需要共享一些系統資源,比如記憶體、印表機、攝影機等。舉個例子:我們去學校列印店列印論文,你按下了 WPS 的 「列印」 選項,於是印表機開始工作。 你的論文列印到一半時,另一位同學按下了 Word 的 「列印」 按鈕,開始列印他自己的論文。想像一下如果兩個進程可以隨意的、並發的共享印表機資源,會發生什麼情況?
顯然,兩個進程並發運行,導致印表機設備交替的收到 WPS 和 Word 兩個進程發來的列印請求,結果兩篇論文的內容混雜在一起了。
進程互斥(mutual exclusion)就是用來解決這個問題的。當某個進程 A 在訪問印表機時,如果另一個進程 B 也想要訪問印表機,它就必須等待,直到 A 進程訪問結束並釋放印表機資源後,B 進程才能去訪問。
實際上,像上述的印表機這種在一個時間段內只允許一個進程使用的資源(這也就是互斥的意思),我們將其稱為臨界資源,對臨界資源進行訪問的那段程式碼稱為臨界區。
通俗的對比一下進程互斥和進程同步:
-
進程同步:進程 A 應在進程 B 之前執行
-
進程互斥:進程 A 和進程 B 不能在同一時刻執行
從上不難看出,進程互斥是一種特殊的進程同步,即逐次使用臨界資源,也是對進程使用資源的先後執行次序的一種協調。
3. 常見的進程同步與互斥機制
常見的進程同步與互斥機制有兩種:
-
訊號量與 PV 操作
-
管程
① 訊號量與 PV 操作
包交包會!看完下面這段解釋你絕對能夠明白 PV 操作是啥。
1965年,荷蘭學者 Dijkstra 提出了一種卓有成效的實現進程同步和互斥的方法 — 訊號量機制(Semaphore)。訊號量其實就是一個變數 ,我們可以用一個訊號量來表示系統中某種資源的數量,比如:系統中只有一台印表機,就可以設置一個初值為 1 的訊號量。
用戶進程可以通過使用作業系統提供的一對原語來對訊號量進行操作,從而很方便的實現進程互斥或同步。這一對原語就是 PV 操作:
1)P 操作:將訊號量值減 1,表示申請佔用一個資源。如果結果小於 0,表示已經沒有可用資源,則執行 P 操作的進程被阻塞。如果結果大於等於 0,表示現有的資源足夠你使用,則執行 P 操作的進程繼續執行。
可以這麼理解,當訊號量的值為 2 的時候,表示有 2 個資源可以使用,當訊號量的值為 -2 的時候,表示有兩個進程正在等待使用這個資源。不看這句話真的無法理解 V 操作,看完頓時如夢初醒。
2)V 操作:將訊號量值加 1,表示釋放一個資源,即使用完資源後歸還資源。若加完後訊號量的值小於等於 0,表示有某些進程正在等待該資源,由於我們已經釋放出一個資源了,因此需要喚醒一個等待使用該資源(就緒態)的進程,使之運行下去。
我覺得已經講的足夠通俗了,不過對於 V 操作大家可能仍然有困惑,下面再來看兩個關於 V 操作的問答:
問:訊號量的值 大於 0 表示有臨界資源可供使用,這個時候為什麼不需要喚醒進程?
答:所謂喚醒進程是從就緒隊列(阻塞隊列)中喚醒進程,而訊號量的值大於 0 表示有臨界資源可供使用,也就是說這個時候沒有進程被阻塞在這個資源上,所以不需要喚醒,正常運行即可。
問:訊號量的值 等於 0 的時候表示沒有臨界資源可供使用,為什麼還要喚醒進程?
答:V 操作是先執行訊號量值加 1 的,也就是說,把訊號量的值加 1 後才變成了 0,在此之前,訊號量的值是 -1,即有一個進程正在等待這個臨界資源,我們需要喚醒它。
訊號量和 PV 操作具體的定義如下:
實現進程互斥
兩步走即可實現進程的互斥:
-
定義一個互斥訊號量,並初始化為 1
-
把對於臨界資源的訪問置於 P 操作和 V 操作之間
P 操作和 V 操作必須成對出現。缺少 P 操作就不能保證對臨界資源的互斥訪問,缺少 V 操作就會導致臨界資源永遠得不到釋放、處於等待態的進程永遠得不到喚醒。
實現進程同步
回顧一下進程同步,就是要各並發進程按要求有序地運行。
舉個例子,以下兩個進程 P1、P2 並發執行,由於存在非同步性,因此二者交替推進的次序是不確定的。假設 P2 的 「程式碼4」 要基於 P1 的 「程式碼1」 和 「程式碼2」 的運行結果才能執行,那麼我們就必須保證 「程式碼4」 一定是在 「程式碼2」 之後才會執行。
如果 P2 的 「程式碼4」 要基於 P1 的 「程式碼1」 和 「程式碼2」 的運行結果才能執行,那麼我們就必須保證 「程式碼4」 一定是在 「程式碼2」 之後才會執行。
使用訊號量和 PV 操作實現進程的同步也非常方便,三步走:
-
定義一個同步訊號量,並初始化為當前可用資源的數量
-
在優先順序較高的操作的後面執行 V 操作,釋放資源
-
在優先順序較低的操作的前面執行 P 操作,申請佔用資源
配合下面這張圖直觀理解下:
生產者和消費者問題
下面我們利用訊號量和 PV 操作來解決經典的進程同步和互斥問題:生產者和消費者問題。
【問題描述】:系統中有一組生產者進程和一組消費者進程,生產者進程每次生產一個產品放入緩衝區,消費者進程每次從緩衝區中取出一個產品並使用。任何時刻,只能有一個生產者或消費者可以訪問緩衝區。
由題可知,生產者、消費者共享一個初始為空、大小為 n 的緩衝區,我們從題目中提煉出同步與互斥關係:
-
同步關係 1:只有緩衝區沒滿時(優先順序高),生產者才能把產品放入緩衝區(優先順序低),否則必須等待
-
同步關係 2:只有緩衝區不空時(優先順序高),消費者才能從中取出產品(優先順序低),否則必須等待
-
互斥關係:緩衝區是臨界資源,各進程必須互斥地訪問。
既然這個題目有兩個同步關係和一個互斥關係,那麼我們就需要兩個同步訊號量和一個互斥訊號量:
-
empty:同步訊號量(對應同步關係 1),表示生產者還能生產多少,即還能放入緩衝區多少產品,該數量小於等於 0,則生產者不能進行生產。 初始化為 n。
-
full:同步訊號量(對應同步關係 2),表示消費者還能從緩衝區取出多少,即當前緩衝區已有產品的數量,該數量小於等於 0,則消費者不能進行讀取。初始化為 0。
-
mutex:互斥訊號量,實現對緩衝區的互斥訪問。初始化為 1。
程式碼如下,注意各個 PV 操作的配對:
② 管程
管程有一個重要特性:在一個時刻只能有一個進程使用管程。進程在無法繼續執行的時候不能一直佔用管程,否則其它進程將永遠不能使用管程。也就是說管程天生支援進程互斥。
其實使用管程是能夠實現訊號量的,並且也能用訊號量實現管程。但是管程封裝的比較好,相比起訊號量來需要我們編寫的程式碼更少,更加易用,這也就是 Java 採用管程機制的原因,synchronized
關鍵字及 wait()
、notify()
、notifyAll()
🎉 關注公眾號 | 飛天小牛肉,即時獲取更新
-
-
並推薦個人維護的開源教程類項目: CS-Wiki(Gitee 推薦項目,現已累計 1.4k+ star)