為什么會出現管程
問題:
1.信號量機制的不足:編寫困難,易出錯
管程的定義
1.是一個特殊的模塊
2.有一個名字
3.由關於共享資源的數據結構及在其上操作的一組過程組成
進程與管程的關系
1.進程只能通過調用管程中的過程來間接的訪問管程中的數據結構
管程要保證什么
1.作為一種同步機制,管程要解決2個問題
1.互斥,管程是互斥進入的 --為了保證管程中的數據結構的數據完整性,管程的互斥性是由編譯器負責保證的
2.同步,管程中設置條件變量及等待/喚醒操作以解決同步問題
1.可以讓一個進程或線程在條件變量上等待(此時,應先釋放管程的使用權),也可以通過發送信號將等待在條件變量上的進程或線程喚醒
應用管程時遇到的問題
場景:
當一個進入管程的的進程執行等待操作時,它應當釋放管程的互斥權
當后面的進入管程的進程執行喚醒操作時(例如P喚醒Q),管程中便存在2個同時處於活動狀態的進程
如何解決:
三種處理方法:
1.P等待Q執行(Hoare)
2.Q等待P繼續執行(MESA)
3.規定喚醒操作為管程中最后一個可執行的操作
(Hansen並發pascal編程語言做了實現)
HOARE管程
因為管程是互斥進入的,所以當一個進程試圖進入一個已被占用的管程時,應當在管程的入口處等待
1.為此,管程的入口處設置進程等待隊列,稱作入口等待隊列
如果進程P喚醒進程Q,則P等待Q執行,如果進程Q執行中又喚醒進程R,則Q等待R執行。如此,在管程內部可能會出現多個等待進程
1.在管程內需要設置一個進程等待隊列,稱為緊急等待隊列,緊急等待隊列的優先級高於入口等待隊列的優先級
條件變量的實現
1.條件變量 -- 在管程內部說明和使用的一種特殊類型的變量
2.var c:condition
3.對於條件變量,可以執行wait和signal操作
wait(c)
如果緊急等待隊列非空,則喚醒第一個等待者,否則釋放管程的互斥權,執行次操作的進程進入c鏈末尾
signal(c)
如果c鍵為空,則相當於空操作,執行此操作的進程繼續執行,否則喚醒第一個等待者,執行此操作的進程進入緊急等待隊列的末尾
管程的實現
1.直接構造 ->效率高
2.間接構造 ->用某種已經實現的同步機制去構造
用管程解決生產者消費者問題
//偽代碼實現
monitor ProducerConsumer
condition full emptery;
integer count;
producers insert(items:interger);
begin
if count == N then wait(full);
insert_item(item);count++;
if count == 1 then signal(empty);
end;
function remove:integer;
begin
if count == 0 then wait(empty)
remove = remove.item;count--;
if count == N -1 then signal(full)
end
count=0
end monitor
//假設編譯器提供了相應的安全接口
procdure producer;
begin
while true do
begin
item = produce_item;
ProducerConsume.insert(item)
end
end
produre consumer
begin
while true do
begin
item = ProducerConsumer.remove()
consume_item(item)
JAVA中的類似機制
public class ProducerConsumer {
static final intN = 100;
static producer p = new producer()
static consumer = new consumer()
static our..monitoer = new our...monitor()
public static void main(String args[]){
p.start()
c.start()
}
static class producer extends Thread{
public void run(){
int item;
while(true){
item = produce;item();
mon.insert(item);
}
}
private int produce item(){...}
}
}
static class consumer extends Thread {
public void run(){
int item
while(true){
item = mon.remove()
consume...item(item);
}
}
private void consume...item(int item){...}
}
static class our...monitor{
private int buffer[] = new int[N]
private int count = 0
public synchronized void insert(int val){
if (count == N)go to sleep()
buffer[h1] = val
h1 = (h1 + 1)%N
count = count + 1
if (count == 1) notify()
}
}
MESA管程
Hoare管程的一個缺點
1.兩次額外的進程切換
解決:
1.signal -> notify
2.notify:當一個正在管程中的進程執行notify(x)時,它使得x條件隊列得到通知,發信號進程繼續執行
使用NOTIFY要注意的問題
1.notify的結果:位於條件隊列頭的進程在將來合適的適合且當處理器可用時恢復執行
2.由於不能保證在它之前沒有其他進程進入管程,因而這個進程必須重新檢查條件
1.用while循環取代if語句
3.導致對條件變量至少多一次額外的檢測,但不再又額外但進程切換,並且對等待進程在notify之后何時運行沒有任何限制
MESA管理:生產者/消費者問題
void append(char x)
{
while(count ==N)cwait(notfull)
buffer[nextin] = x;
nextin = (nextin + 1 )% N
count ++ ;
cnotify(noempty)
}
void take(char x)
{
while(count == 0)cwait(notfull)
x = buffer[nextout];
nextout = [nextout + 1] % N;
count --.
cnotify(notfull)
}
改進notify
對notify的一個很有用的改進
1.給每個條件原語關聯一個監視計時器,不論是否被通知,一個等待時間超時的進程將被設為就緒態
2.當該進程被調度執行時,會再次檢查相關條件,如果條件滿足則繼續執行
超時可以防止如下情況的發生
1.當某些進程在產生相關條件的信號之前失敗時,等待該條件的進程就會被無限制的推遲執行而處於飢餓狀態
引入BROADCAST
broadcast:所有在該條件上等待的進程都被釋放並進入就緒隊列
1.當一個進程不知道又多少進程將被激活時,這種方式是非常方便的
例子:生產者/消費者問題中,假設insert和remove函數都使用於可變長度的字符塊。它不需要知道每個正在等待的消費者准備消耗多少字符,而僅僅執行一個broadcast,所有正在等待的進程都得到通知並再次嘗試執行
2.當一個進程難以准確判斷將激活哪個進程時,也可以使用廣播
HOARE管程與MESA管程的比較
1.MESA管程優於Hoare管程之處在於Mesa管程錯誤比較少
2.在Mesa管程中,由於每個過程在收到信號后都重新檢查管程變量,並且由於使用來while結果,一個進程不正的broadcast廣播或發信號notify,不會導致收到信號的程序出錯
收到信號的程序將檢查相關的變量,如果期望的條件沒有滿足,它會重新繼續等待
管程小結
管程:抽象數據類型
有一個明確定義的操作集合,通過它且只有通過它才能操作該數據類型的實例
實現管程結構必須保證下面幾點
1.只能通過管程的某個過程在能訪問資源
2.管程是互斥的,某個時刻只能又一個進程或線程調用管程中的過程
條件變量:為提供進程與其他進程通信或同步而引入
1.wait/signal或wait/notify或wait/broadcast
PTHREAS中的同步機制
1.通過互斥量,保護臨界區
Pthread...mutex...lock
Pthread...mutex...unlock
2.條件變量,解決同步
Pthread...cond...wait
Pthread...cond...signal
int main(int argc, char ""argv){
pthread...t.pro.con;
pthread...mutex...init(&the...mutex,0);
pthread...cond...init(&condc,0);
pthread...cond...init(&condp,0);
pthread...create(&con,0,consumer,0);
pthread...create(&pro,0,producer,0);
pthread...join(pro,0);
pthread...join(con,0);
pthread...cond...destroy(&condc);
pthread...cond...destroy(&condp);
pthread...mutex...destroy(&the...mutex);
}
#include <stdio.h>
#include <pthread.h>
#define MAX 1000000000
pthread...mutex...t the...mutex
pthread...cond...t condc,condp
int buffer = 0
void *producer(void *ptr)
{
int i ;
for (i = 1 ; i <= MAX; i++){
pthread...mutex...lock(&the..mutex)
while(buffer != 0)pthread...cond...wait(&condp,&the...mutex)
buffer = i;
pthread...cond...signal(&condc);
pthread...mutex...unlock(&the...mutex);
}
pthread...exit(0)
}
void *consumer(void *ptr)
{
int i;
for(i = 1; i < MAX; i++){
pthrea...mutex...lock(&the...mutex)
while (buffer == 0)pthread...cond...wait(&condc,&the...mutex)l
buffer = 0;
pthread...cond...signal(&condp)
pthread...mutex...unlock(&the...mutex)
}
}
PTHREAD_COND_WAIT
1.pthread_cond_wait的執行分解為三個主要動作
1.解鎖
2.等待,當收到解除等待的信號(pthread_con_signal)或者(pthread_con_broad_cast)之后,pthread_cond_wait馬上需要要做的動作是
3.上鎖