1.為什麼要引入管程
我們在日常使用信号量機制時編寫程式困難還容易出錯,因為會有大量的PV操作散落在各個程序當中,順序不易管理,容易發生死鎖。
1973年,Brinch Hansen首次在程式設計語言(Pascal)中引入了“管程”成分——一種進階的同步機制
目的:
- 把分散在各個程序中的臨界區集中管理
- 防止程序的非法同步操作
- 便于程式設計
管程是由局限于自己的若幹共享變量(資料結構)及其說明,和所有通路這些共享變量的過程所組成的軟體子產品。
基本思想:
把信号量及其操作原語封裝在一個對象中,即将共享資源及其操作集中在一個子產品中。管程可以使用函數庫的形式實作,衆多語言中都已經實作了管程。
簡單地說管程就是一個概念模型,任何語言都可以實作
2.管程的定義和基本特征
管程的組成結構:
- 局部共享變量和條件變量組成管程内的資料結構;
- 對資料結構進行操作的一組過程 (“過程”可以了解為“函數”)
- 對資料結構進行初始化的語句
- 管程有一個名字
管程的基本特征:
- 局限于管程的共享變量(資料結構)隻能被管程中的過程通路;
- 一個程序通過調用管程中的任一過程進入管程;
- 任何時候隻能有一個程序在管程中執行,調用管程的其他程序将被挂起,以等待管程變為可用,即管程有效地實作互斥。進入管程的互斥機制由編譯器負責,通常使用信号量
- 管程可被系統範圍内的程序互斥通路,屬于共享資源
monitor ProducerConsumer
condition full, empty;//條件變量用來實作同步
int count = 0 ;//緩沖區中的産品數
void insert (Item item){//把産品item放入緩沖區中
if(count == N)wait(full);//如果緩沖區滿了等待
count++;
insert_item(item);
if(count == 1)signal(empty);
}
Item remove(){//重緩沖區中取出一個産品
if(count == 0)wait(empty);
count--;
if(count == N=1)signal(full);
return remove_item();
}
end monitor;
//生産者程序
producer(){
while(1){
item = 生産一個産品;
ProdecerConsumer.insert(item);
}
}
//消費者程序
consumer(){
while(1){
item = ProdecerConsumer.remove();
消費産品item;
}
}
根據代碼我們可以看出生産者生産的産品與消費者消費的産品作為參數傳進管程當中,比PV操作簡單了許多,緩沖區空了與互斥都不用生産者與消費者擔心,都是管程需要解決的問題,有編譯器負責實作各個程序互斥地進入管程中的過程,每次僅允許一個程序在管程内執行某個内部過程。管程中設定條件變量和等待/喚醒操作,以解決同步問題。
并發程式設計領域的兩大問題–互斥、同步,都可以通過管程來解決。管程的解決方法如下:
互斥:管程将共享變量及對共享變量的操作統一封裝起來,并通過一個互斥鎖來保證同一時刻隻允許一個程序進入管程,以實作對共享變量的互斥通路。這個互斥鎖一般是由編譯器自動添加的。
同步(時序):管程中引入了條件變量,而且每個條件變量都對應有一個等待隊列,通過這兩者來提供同步的機制。條件變量有wait()、signal()兩種操作原語,管程中的過程通過調用條件變量的操作原語來封裝了程序的同步操作,使程序在條件不滿足時被阻塞,條件滿足時被喚醒。程序調用管程中的過程就可以實作同步。但管程的過程裡何時應該阻塞程序,何時喚醒程序都是由程式員自己決定的,管程模型并不知道你的程式邏輯。
管程和信号量是等價的,所謂等價指的是用管程能夠實作信号量,也能用信号量實作管程。但是管程更容易使用,是以 Java 選擇了管程。
管程就像程式封裝的思想,把程序同步互斥這些複雜的細節放進管程定義的函數之内,而我們調用隻需要函數接口即可。