1. 程式人生 > >管程(Monitor)概念及Java的實現原理

管程(Monitor)概念及Java的實現原理

### 互斥 互斥訪問是併發程式設計要解決的核心問題之一。 有許多種方法可以滿足臨界區的互斥訪問。大體上可以分為三種, 一種是軟體方法,即由使用者程式承擔互斥訪問的責任,而不需要依賴程式語言或作業系統,譬如Dekker演算法、Peterson演算法等,通常這種方式會有一定的效能開銷和程式設計難度。 第二種是作業系統或程式語言對互斥的原生支援,譬如Linux中的mutex、Java語言的synchronized。 最後是硬體上的特殊指令,譬如著名的CAS。這種方式開銷最少,但是很難成為一種通用的解決方案,通常作業系統或程式語言的互斥是基於此建立起來的。 ### 管程-Monitor 管程屬於程式語言級別的互斥解決方案,最早是Brinch Hanson和Hoare於1970s提出的概念,已在Pascal、Java、Python等語言中得到了實現。 “管程”一詞翻譯自英文`Monitor Procedures`,字面理解就是管理一個或多個執行過程。(但是個人感覺“管程”這個翻譯有點莫名其妙,看完更迷糊了,所以本文堅持用回原名Monitor。) Monitor本質上是對`通用同步工具`的一種抽象,它就像一個執行緒安全的盒子,使用者程式把一個方法或過程(程式碼塊)放進去,它就可以為他們提供一種保障:**同一時刻只能有一個程序/執行緒執行該方法或過程,從而簡化了併發應用的開發難度**。 如果Monitor內沒有執行緒正在執行,則執行緒可以進入Monitor執行方法,否則該執行緒被放入`入口佇列`(entry queue)並使其掛起。當有執行緒從Monitor中退出時,會喚醒entry queue中的一個執行緒。 為了處理併發執行緒,Monitor還需要一個更基礎的同步工具,或者說需要一個機制,使得執行緒不僅被掛起,而且還能釋放Monitor,以便其他執行緒可以進入。 Monitor使用`條件變數`(Condition Variable)支援這種機制,這些條件變數(一個或多個)包含在Monitor中,並且只有在Monitor內才能被訪問 (類似Java物件的private變數)。 對外開放兩個方法以便使用者程式操作`條件變數`: `cwait(c)`:呼叫該方法的執行緒在條件c上阻塞,monitor現在可以被其他執行緒使用。 `csignal(c)`:恢復在條件c上被阻塞的執行緒。若有多個這樣的執行緒,選擇其中一個。 (通常,為了保證cwait/csignal對條件變數的變更是原子性的,還需要藉助CAS) ### 當執行緒等待資源時 當Monitor中正在執行的執行緒無法獲取所需資源時,情況會變得更加複雜。 如果發生這種情況,等待資源的執行緒可以先把自己掛起,並且釋放Monitor的使用權,使得其他執行緒得以進入Monitor。 那麼問題來了,當第二個執行緒在執行期間,第一個執行緒所需的資源可用了,會發生什麼? 立即喚醒第一個執行緒,還是第二個執行緒先執行完? 對此產生了多個對Monitor的定義。 ### Hoare版本 在Hoare的語義中,當資源可用時,ThreadA立即恢復執行,而ThreadB進入signal queue。 ``` 1.ThreadA 進入 monitor 2.ThreadA 等待資源 (進入wait queue) 3.ThreadB 進入monitor 4.ThreadB 資源可用 ,通知ThreadA恢復執行,並把自己轉移到signal queue。 5.ThreadA 重新進入 monitor 6.ThreadA 離開monitor 7.ThreadB 重新進入 monitor 8.ThreadB 離開monitor 9.其他在entry queue中的執行緒通過競爭進入monitor ``` ### Mesa版本 在Mesa Monitor的實現中,第二個執行緒會先執行完。 ThreadA的資源可用時,把它從wait queue轉移到entry queue。ThreadB繼續執行至結束。 ThreadA最終也會從entry queue中得以執行。 ``` 1.ThreadA 進入 monitor 2.ThreadA 等待資源 (進入wait queue,並釋放monitor) 3.ThreadB 進入monitor 4.ThreadB 資源可用,通知ThreadA。(ThreadA被轉移到entey queue) 5.ThreadB 繼續執行 6.ThreadB 離開monitor 7.ThreadA 獲得執行機會,從entry queue出佇列,恢復執行 8.ThreadA 離開monitor 9.其他在entry queue中的執行緒通過競爭進入monitor ``` 由於ThreadA被轉移到了entry queue,當ThreadB退出monitor後,ThreadA與其他執行緒平等競爭monitor的進入條件,所以並不能保證立即執行。 更不幸的是,等到ThreadA重入monitor後,資源可能再次不可用,重複以上過程。 ### Brinch Hanson版本 Brinch Hanson Monitor(以下簡稱BH Monitor)只允許執行緒從monitor退出時發出訊號,此時被通知的執行緒進入monitor恢復執行。 ``` 1.ThreadA 進入 monitor 2.ThreadA 等待資源a 3.ThreadB 進入monitor 4.ThreadB 離開Monitor,並給通知等待資源a的執行緒,資源可用 5.ThreadA 重新進入 monitor 6.ThreadA 離開monitor 7.其他執行緒從entry queue中競爭進入monitor ``` ###三種語義對比 Hoare Monitor中,資源可用時,ThreadB呼叫csignal()後被阻塞,以便ThreadA立即恢復執行。 這時ThreadB應該被放到哪裡?一種可能是轉移到entry queue,這樣它就必須與其他還未進入Montior的執行緒平等競爭獲取重入機會。 但是由於在呼叫csignal()之前,ThreadB已經執行了一部分,因此使它優先於其他執行緒是有意義的, 為此,Hoare Monitor增加了signal queue用於存放阻塞在csignal()上的執行緒。 Hoare Monitor的一個明顯缺點是,ThreadB在執行中途被中斷,需要額外的兩次執行緒切換才能恢復執行。 不同的是,Mesa Monitor和BH Monitor會保證ThreadB先執行完,因此不需要額外的signal queue。 ### Java版本的Monitor Java在實現時對最初的Monitor定義做了一些合理的限制。首先,與以上三種都不一樣的是,Java Montior只允許一個`條件變數`,而不是多個。 不像BH monitor,signal可以出現在程式碼的任何地方。 也不像Hoare monitor,資源可以時,被通知的執行緒不會立即執行,而是從BLOCK狀態變成RUNNABLE狀態,被CPU再次排程到時才恢復執行。 與cwait(c)和csignal(c)對應的是wait()和notify()方法。 Java monitor機制通過`synchronized`關鍵字暴露給使用者,syncronized可以使用者修飾方法或程式碼塊,兩者本質上都是一個執行過程。 ###Java monitor實現生產者/消費者 ``` //簡化版本,只允許一個生產者和一個消費者 class BoundedBuffer { private int numSlots = 0; private double[] buffer = null; private int putIn = 0, takeOut = 0; private int count = 0; public BoundedBuffer(int numSlots) { if (numSlots <= 0) throw new IllegalArgumentException("numSlots<=0"); this.numSlots = numSlots; buffer = new double[numSlots]; System.out.println("BoundedBuffer alive, numSlots=" + numSlots); } public synchronized void deposit(double value) { while (count == numSlots) try { wait(); } catch (InterruptedException e) { System.err.println("interrupted out of wait"); } buffer[putIn] = value; putIn = (putIn + 1) % numSlots; count++; if (count == 1) notify(); //喚醒等待的consumer } public synchronized double fetch() { double value; while (count == 0) try { wait(); } catch (InterruptedException e) { System.err.println("interrupted out of wait"); } value = buffer[takeOut]; takeOut = (takeOut + 1) % numSlots; count--; // wake up the producer if (count == numSlots-1) notify(); // 喚醒等待的producer return value; } } ``` > 1.Monitors and Condition Variables:https://cseweb.ucsd.edu/classes/sp17/cse120-a/applications/ln/lecture8.html > 2.《作業系統精髓與設計原理》第五章 > 3.https://en.m.wikipedia.org/wiki/Monitor_(synchroni