2011-07-04 68 views
6

我對併發編程有點新,並試圖理解使用Monitor.Pulse和Monitor.Wait的好處。什麼是Monitor.Pulse和Monitor.Wait的優點?

MSDN的例子如下:

class MonitorSample 
{ 
    const int MAX_LOOP_TIME = 1000; 
    Queue m_smplQueue; 

    public MonitorSample() 
    { 
     m_smplQueue = new Queue(); 
    } 
    public void FirstThread() 
    { 
     int counter = 0; 
     lock(m_smplQueue) 
     { 
      while(counter < MAX_LOOP_TIME) 
      { 
       //Wait, if the queue is busy. 
       Monitor.Wait(m_smplQueue); 
       //Push one element. 
       m_smplQueue.Enqueue(counter); 
       //Release the waiting thread. 
       Monitor.Pulse(m_smplQueue); 

       counter++; 
      } 
     } 
    } 
    public void SecondThread() 
    { 
     lock(m_smplQueue) 
     { 
      //Release the waiting thread. 
      Monitor.Pulse(m_smplQueue); 
      //Wait in the loop, while the queue is busy. 
      //Exit on the time-out when the first thread stops. 
      while(Monitor.Wait(m_smplQueue,1000)) 
      { 
       //Pop the first element. 
       int counter = (int)m_smplQueue.Dequeue(); 
       //Print the first element. 
       Console.WriteLine(counter.ToString()); 
       //Release the waiting thread. 
       Monitor.Pulse(m_smplQueue); 
      } 
     } 
    } 
    //Return the number of queue elements. 
    public int GetQueueCount() 
    { 
     return m_smplQueue.Count; 
    } 

    static void Main(string[] args) 
    { 
     //Create the MonitorSample object. 
     MonitorSample test = new MonitorSample();   
     //Create the first thread. 
     Thread tFirst = new Thread(new ThreadStart(test.FirstThread)); 
     //Create the second thread. 
     Thread tSecond = new Thread(new ThreadStart(test.SecondThread)); 
     //Start threads. 
     tFirst.Start(); 
     tSecond.Start(); 
     //wait to the end of the two threads 
     tFirst.Join(); 
     tSecond.Join();   
     //Print the number of queue elements. 
     Console.WriteLine("Queue Count = " + test.GetQueueCount().ToString()); 
    } 
} 

,我不能看到使用等待的好處,而是脈衝這樣的:

public void FirstThreadTwo() 
    { 
     int counter = 0; 
     while (counter < MAX_LOOP_TIME) 
     { 
      lock (m_smplQueue) 
      { 
       m_smplQueue.Enqueue(counter); 
       counter++; 
      } 
     } 
    } 
    public void SecondThreadTwo() 
    { 
     while (true) 
     { 
      lock (m_smplQueue) 
      { 
        int counter = (int)m_smplQueue.Dequeue(); 
        Console.WriteLine(counter.ToString()); 
      } 
     } 
    } 

任何幫助最讚賞。 謝謝

回答

12

爲了描述「優點」,一個關鍵問題是「過了什麼?」。如果你的意思是「優先於熱循環」,那麼CPU利用率是顯而易見的。如果你的意思是「優先於睡眠/重試循環」 - 你可以得到更快的響應(Pulse不需要等待很長時間)使用較低的CPU(你沒有不必要地喚醒2000次)。

儘管人們的意思是「優先於Mutex等」。

我傾向於廣泛使用這些,甚至優先於互斥,重置事件等;原因:

  • 他們是簡單,覆蓋了大部分我需要
  • 他們是相對便宜的情景,因爲他們並不需要所有的方式去OS句柄(不像互斥等,這是一家擁有由OS)
  • 我一般已經使用lock處理同步,所以有很好的機會,我已經有一個lock當我需要等待一些
  • 它實現了我的正常目的 - 允許2個線程以管理方式完成對方的信號
  • 我很少需要互斥等的其他特徵(如被進程間)
+0

嘿,謝謝你的快速回復。 關於什麼問題 - 結束使用Monitor.Enter和Monitor.Exit, 我真的不知道Pulse和Wait的差別如何比使用這兩種方法 - 只是在性能成本方面。 – seren1ty

+0

@ seren1ty他們完全***不同的事情;進入/退出獲取並釋放一個鎖;等待釋放鎖,進入等待隊列(等待脈衝),然後(當喚醒時)重新獲得鎖; Pulse將等待的項目從等待隊列移動到就緒隊列。 ***完全不同(但免費)使用。脈衝/等待用於*線程之間的座標*,而不需要熱循環或冷循環。 –

3

這是一個使用Monitor.Pulse/Wait的性能改進,正如您猜測的那樣。獲取鎖是一個相對昂貴的操作。通過使用Monitor.Wait,你的線程將睡眠,直到其他線程用`Monitor.Pulse'喚醒你的線程。

您將看到TaskManager中的差異,因爲即使沒有任何內容在隊列中,也會掛起一個處理器內核。

4

您的代碼段中存在嚴重缺陷,SecondThreadTwo()在嘗試調用空隊列上的Dequeue()時會失敗。您可能通過讓FirstThreadTwo()在消費者線程之前執行一小部分時間,可能通過首先啓動它來實現它。這是一個意外,一旦運行這些線程一段時間後會停止工作,或者使用不同的機器負載啓動它們。這可能意外地無誤地工作了很長時間,很難診斷偶爾的故障。

沒有辦法編寫鎖定算法來阻塞使用者,直到隊列變爲非空而只有鎖定語句。一個繁忙的循環不斷地進入和退出鎖定工作,但是是一個非常糟糕的替代品。

寫這樣的代碼最好留給線程領導人,很難證明它在所有情況下都能正常工作。不只是沒有像這樣的失敗模式或線程比賽。而且避免死鎖,活鎖和線程車隊的算法的一般適用性。在.NET世界中,大師是Jeffrey Richter和Joe Duffy。他們在他們的書籍,他們的博客和雜誌文章中吃早餐鎖定設計。竊取他們的代碼是可以接受的。並且在System.Collections.Concurrent命名空間中增加了一部分內容進入.NET框架。

0

Pulse的優點和Wait是它們可被用作構建塊的所有其他同步機制包括互斥,事件障礙等等。有些事情可以用PulseWait來完成,BCL中的任何其他同步機制都無法完成。

所有有趣的東西發生在Wait方法。 Wait將退出臨界區,並通過將其置於等待隊列中,將線程置於WaitSleepJoin狀態。一旦調用Pulse,則等待隊列中的下一個線程將移至就緒隊列。一旦線程切換到Running狀態,它將重新進入關鍵部分。重複另一種方式很重要。 Wait將以原子方式釋放鎖並重新獲取它。沒有其他同步機制具有此功能。

想象這個最好的方法是嘗試複製一些其他策略的行爲,然後看看會出現什麼問題。讓我們用ManualResetEvent試試這個excerise,因爲SetWaitOne方法似乎是,因爲它們可能是類似的。我們的第一次嘗試可能是這樣的。

void FirstThread() 
{ 
    lock (mre) 
    { 
    // Do stuff. 
    mre.Set(); 
    // Do stuff. 
    } 
} 

void SecondThread() 
{ 
    lock (mre) 
    { 
    // Do stuff. 
    while (!CheckSomeCondition()) 
    { 
     mre.WaitOne(); 
    } 
    // Do stuff. 
    } 
} 

應該很容易看出代碼會死鎖。那麼如果我們嘗試這種天真的修復會發生什麼?

void FirstThread() 
{ 
    lock (mre) 
    { 
    // Do stuff. 
    mre.Set(); 
    // Do stuff. 
    } 
} 

void SecondThread() 
{ 
    lock (mre) 
    { 
    // Do stuff. 
    } 
    while (!CheckSomeCondition()) 
    { 
    mre.WaitOne(); 
    } 
    lock (mre) 
    { 
    // Do stuff. 
    } 
} 

你能看到這裏會出現什麼問題嗎?由於在等待條件被檢查後我們沒有自動重新進入鎖,所以另一個線程可能會進入並使條件無效。換句話說,另一個線程可能會執行一些操作,導致CheckSomeCondition在重新獲取以下鎖之前再次開始返回false。如果你的第二塊代碼要求條件爲true,那肯定會造成很多奇怪的問題。

相關問題