Monitors

monitor Condition variable


管程


管程的定義
管程是對共享數據的訪問進行控制的特殊的一段程序:

  • 這是編譯器的一段代碼,在運行的時候執行

管程是一個壓縮了如下內容的模塊:

  • 共享的數據結構
  • 對共享數據的操作
  • 在不同線程之間的同步操作

它能夠:

  • 保證數據不會有非同步的訪問
  • 只允許線程以合理的方式訪問共享數據

管程的語義

  • 管程保證互斥訪問:

  • 一次只能有一個線程“在管程中”(也就是執行管程提供的程序)

  • 如果一個線程在管程中的時候,有第二個線程調用管程中的程序,則這第二個線程會被阻塞。

  • 如果在管程中的線程被阻塞了,則另外一個線程就可以進入管程

  • 問題:

  • 管程和我們了解的臨界區有什么區別呢?

我的理解:管程包括了對共享數據的處理函數。用戶只能使用管程提供的方式,不能自定義;但在臨界區中用戶可以對共享數據做任何事情。臨界區是指會對共享數據產生data race的代碼段,無論是否采取保護措施臨界區都是存在的。而管程可以被認為是對臨界區進行保護的一段代碼,包裝了臨界區和對其互斥訪問的管理。

  • 管程的并行是什么含義呢?

管程的使用實例

Monitor account {
  double balance;
  double withdraw(amount) {
  balance = balance –amount;
  return balance;
  }
}
例子


如果一個線程想在管程中等待呢?——條件變量(condition variables)

Attention:

共享變量并不是if語句中的判斷條件,所以不能if(condition) { ... }

條件變量是:

  • Sleep:線程需要的資源無法獲取的時候,讓線程等待
  • Wake(or signal):資源可以訪問的時候叫醒線程
  • 'Wakeall (or signalAll)':叫醒所有等待線程
    條件變量就是實現上述操作的一種方式

條件變量 & 鎖

條件變量不能代替鎖,而是和鎖互補的一種機制

  • Wait(condition, lock)

首先釋放鎖,將線程放到condition的等待隊列中,如果線程再醒過來,將重新獲得鎖。
在放在等待隊列的時候,會讓線程sleep,等sleep返回的時候,它是被另外一個持有鎖的線程叫醒的。

  • Signal(condition)

叫醒一個在condition的等待隊列上的線程

  • Broadcast(condition)

叫醒所有在 condition的等待隊列上的線程

條件變量 & 管程:

管程中的一個條件變量一般表示的含義是:一個線程要在管程中繼續運行所需要的條件(比如消費者需要資源不為空才能消費)

Monitor M {
    ...monitored variables;
    Condition c, d;
    
    void enter_monitor(...) {
        if( extra property C not true) wait(c); //等待管程的鎖
        do what you have to do 
        if(extra property true D) signal(d);//叫起在d上等待的線程
    }
}

一般條件變量對應的操作為:
Wait( ) : 等待管程的鎖,或者等待條件變量被signal了
`Signal( ):’ 叫醒一個等待線程
'Broadcase( ):' 叫醒所有等待線程


條件變量 != 信號量
有條件變量的管程 != 信號量

但它們可以相互實現

其區別在于,對管程訪問用鎖控制,下面是具體分析:

  • wait()操作會阻塞當前線程,并放開鎖
  • 線程要能進行wait()操作,必須在管程內部,也就是必須持有鎖
  • Semaphore::P只是阻塞了在隊列上的線程,線程還沒有持有信號量
  • Signal()使得一個等待線程被喚醒
  • 如果沒有等待的線程,這個信號其實沒有用
  • 但對Semaphore::V()會增加信號量的值,使得將來其他線程能訪問
  • 也就是Condition沒有歷史,但Semaphore是有的。


使用管程和條件變量解決實際問題


(一)有限緩沖區問題描述:
有一個被producer和consumer共享的資源池

  • producer向其中放入資源
  • consumer從其中拿出資源消耗。

producer和consumer執行速度不同:

  • 沒有嚴格的串行化
  • 任務是獨立執行的
  • 在buffer上不會發生線程切換

安全要求:

  • 如果nc是消費了的資源數,np是生產了的資源數,N是buffer的大小。則0 ≤ np - nc ≤ N

有限緩沖區代碼

Monitor bounded_buffer {
    Resource buffer[N];
    //Variables for indexing buffer
    Condition not_full;  //space in buffer;
    Condition not_empty; //value in buffer
    
    void put_resource (Resource R){
        if(buffer array is full) 
            wait(not_full);
        Add R to buffer array;
        signal(not_empty);
    }
    Resource get_resource() {
        if(buffer array is empty)
            wait(not_empty);
        Get resource R from buffer array;
        signal(not_full);
        return R;
    }
}

(二)讀者寫者問題

分析:

  • 使用Mesa管程,有四個函數StartRead StartWrite EndRead EndWrite
  • nr nw分別表示讀者和寫者的數量
  • nw = 0的時候可以讀
  • 在'(nw = 0) && (nr = 0)`的時候可以寫

實現:

Monitor RW {
    int nr = 0, nw = 0;
    Condition canRead, canWrite;
    
    void StartRead() {
        while(nw != 0) wait(canRead);
        nr++;
    }
    void EndRead() {
        nr--;
        if(nr == 0) signal(canWrite);
    }
    void Start Write() {
        while(nr != 0 || nw != 0) wait(canRead);
        nw++;
    }
    void EndWrite() {
        nw--;
        signal(canWrite);
        signal(canRead);
    }
}

兩種不同的管程實例——Hoare管程和Mesa管程


兩者的區別主要是signal()的語義不一樣

Hoare monitors(original)

  • signal()立刻就會進行線程切換,即將調用signal()的線程切換下CPU,讓被wakeup的線程運行
  • condition一定會被這個被wakeup的線程一直持有

Mesa monitors(Mesa,Java)

  • signal()的時候,是把一個線程叫醒然后放入readyList,然后繼續執行當前線程
  • condition在不一定被這個被wakeup的線程持有,線程再一次進入管程的時候,必須檢查condition是否滿足。(因為可能被readyList上前面的線程用掉了)

具體使用的區別:
Hoare:

if(empty)
    wait(condition)

Mesa:

while(empty)
    wait(condition)

比較:
Mesa管程更易于使用,也更有效

  • 因為上下文的切換比較少,而且容易支持broadcast

Hoare管程則相對不夠靈活

  • 但更容易理解
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容