使用監視器的哲學家就餐問題


作業系統是一種管理計算機的每一個方面的軟體,以便其能夠平穩且正確地執行。因此,作業系統必須同時執行多個任務。同時執行任務對作業系統來說並非真正的問題,但當這些同時執行的任務使用公共資源時,就會發生故障。為了克服這種情況,引入了同步機制,它基本上管理共享相同資源的程序。哲學家就餐問題是一個經典的同步問題。

什麼是哲學家就餐問題?

哲學家就餐問題背後的故事是,它代表了一種場景,一群哲學家圍坐在一張圓桌旁,他們一生都在思考或吃義大利麵。為簡便起見,讓我們假設有五個哲學家坐在桌子旁。圓桌上有五個叉子。為了吃飯,每個哲學家需要兩個叉子;一個在他的左邊,另一個在他的右邊。哲學家一次只能拿起一個筷子。他不能拿起坐在他旁邊另一個哲學家手中已經有的筷子。當哲學家同時擁有兩個筷子時,他開始吃飯而不放下筷子。問題在於設計一種演算法來分配這些有限的資源(筷子)給程序(哲學家),而不會導致死鎖或飢餓。

現在,存在一些可以解決哲學家就餐問題的演算法,但可能會出現死鎖情況。此外,無死鎖情況並不一定無飢餓情況。在解決哲學家就餐問題時,可以使用訊號量,但這可能會導致死鎖。因此,為了避免這些情況,我們使用帶有條件變數的監視器。

什麼是監視器?

抽象資料型別 (ADT) 是一種程式設計概念,它將私有資料與操作資料的公共方法封裝在一起。這意味著資料對外部世界是隱藏的,只能透過一組預定義的方法訪問或操作。

監視器也是一種 ADT,它提供一組程式設計師定義的操作,這些操作在監視器內提供互斥。簡單來說,監視器是一種同步概念,它在訪問共享資源的併發執行緒或程序之間提供互斥和協調。它封裝了共享資源以及一組可用於以執行緒安全的方式訪問和修改該資源的過程。

語法

監視器的虛擬碼:

monitor monitor_name{
   procedure p1(…){
   …
   }
   procedure p2(…){
   …
   }
   .
   .
   .
   procedure pn(…){
   …
   }
   Initialization code (…){
   …
   }
}

什麼是條件變數?

條件變數在監視器內部提供同步。如果一個程序想要休眠或允許一個等待的程序繼續,在這種情況下,監視器中使用條件變數。可以在條件變數上執行兩個操作:wait 和 signal

  • wait() − 呼叫此操作的程序將被掛起並放入該條件變數的阻塞佇列中。

  • signal() − 此操作恢復掛起的程序。

什麼是訊號量?為什麼監視器比訊號量更受青睞?

訊號量是一個整數變數,程序可以使用它來允許它們訪問共享的公共資源。除了初始化之外,訊號量只能透過兩個原子操作訪問:wait()signal()。訊號量有兩種型別:

  • 二元訊號量

  • 計數訊號量

訊號量和監視器都可以用來解決哲學家就餐問題。但是,關於哪一個更好——訊號量或監視器——並沒有明確的答案。這完全取決於問題的具體要求。

一方面,由於其內建的互斥和條件變數,監視器為哲學家就餐問題提供了更簡單直接的解決方案。

另一方面,訊號量需要更復雜的同步程式碼,並且由於前面解釋的原因,更容易出錯和死鎖。

總而言之,雖然監視器和訊號量都用於解決哲學家就餐問題,但監視器比訊號量更方便。

使用監視器的哲學家就餐問題解決方案

使用監視器是因為它們為哲學家就餐問題提供了一個無死鎖的解決方案。它用於訪問所有狀態變數和條件變數。應用監視器後,它施加一個限制,即哲學家只有在他左右兩邊的筷子都可用時才能拿起筷子。

要編寫解決方案程式碼,我們需要區分哲學家可能處於的三種狀態。

  • 思考

  • 飢餓

  • 進食

示例

以下是使用監視器實現哲學家就餐問題的程式碼:

monitor DiningPhilosophers {
   enum {THINKING, HUNGRY, EATING} state[5];
   condition self[5];
   void pickup(int i) {
      state[i] = HUNGRY;
      test(i);
      if (state[i] != EATING) {
         self[i].wait();
      }
   }
   void putdown(int i) {
      state[i] = THINKING;
      test((i + 4) % 5);
      test((i + 1) % 5);
   }
   void test(int i) {
      if (state[(i + 4) % 5] != EATING &&
      state[i] == HUNGRY &&
      state[(i + 1) % 5] != EATING) {
         state[i] = EATING;
         self[i].signal();
      }
   }
   initialization code() {
      for(int i=0;i<5;i++)
      state[i] = THINKING;
   }
}
DiningPhilosophers dp;

在此實現中,筷子的分配由監視器 DiningPhilosophers 控制。在開始吃飯之前,每個哲學家都必須呼叫 pickup() 操作。這表示哲學家很餓,意味著程序想要使用資源。它還在 test() 中僅當哲學家的左鄰和右鄰沒有吃飯時才將狀態設定為 EATING。如果哲學家無法吃飯,則呼叫 wait() 操作。操作成功完成後,哲學家現在可以吃飯了。

記住這一點,哲學家現在呼叫 putdown() 操作。放下叉子後,它會檢查它的鄰居。如果他們很餓並且他們的兩個鄰居都沒有在吃飯,那麼呼叫 signal() 並讓他們吃飯。

因此,哲學家必須同時呼叫 pickup() 和 putdown() 操作,這確保了沒有兩個鄰居同時吃飯,從而實現了互斥。因此,它可以防止死鎖。但是,有可能其中一個哲學家可能會餓死。

優點和缺點

哲學家就餐問題可以使用監視器和訊號量來實現。監視器和訊號量都是併發程式設計中使用的同步結構。但是,對於上述現象,使用監視器而不是訊號量有一些優點和缺點。

優點

  • 易於使用 − 監視器比訊號量更容易使用。透過將初始化程式碼封裝在監視器中,除錯相對容易。

  • 互斥 − 透過在哲學家就餐問題中實現監視器,實現了互斥。因此,它可以防止兩個相鄰的哲學家同時吃飯,從而防止死鎖情況。

  • 條件變數 − 監視器提供條件變數,指示執行緒等待特定條件滿足。

缺點

  • 效能 − 監視器需要對特定資源進行鎖定,這可能會導致不必要的延遲。因此,效能可能會受到影響。

  • 靈活性 − 由於問題的特定同步要求,監視器可能不如訊號量靈活。在更復雜的場景中,訊號量由於其靈活性而更有可能適合。

結論

哲學家就餐問題是作業系統中可能出現的同步問題的示例。但是,透過使用監視器來實現問題的解決方案,可以在共享資源上實現互斥,從而防止死鎖的發生。

監視器提供了一種內建的互斥機制,它一次只允許一個程序訪問一個資源。這確保了在這種情況下,哲學家(程序)需要吃飯的叉子(共享資源)不會併發訪問,從而避免了死鎖情況。

總的來說,透過在哲學家就餐問題中使用監視器,可以防止潛在的同步問題。

更新於:2023年4月7日

9K+ 次瀏覽

開啟您的職業生涯

完成課程獲得認證

開始學習
廣告
© . All rights reserved.