2017-09-21 24 views
3

我讀這篇文章https://www.codeproject.com/Articles/28785/Thread-synchronization-Wait-and-Pulse-demystified#_articleTop當ThreadB Monitor.Pulse(_locker)哪個線程會首先得到_locker?

有一個報價:

推薦模式

這些隊列會導致意外的行爲。發生脈衝時,等待隊列的頭部被釋放並被添加到就緒隊列中。但是,如果就緒隊列中有其他線程,則它們將在釋放線程之前獲取鎖。這是一個問題,因爲獲取鎖的線程可能會改變脈衝線程所依賴的狀態。

的解決方案是使用lock語句內while條件:

readonly object key = new object(); 
bool block = true; 

// thread A 
lock (key) 
{ 
    while (block) 
    Monitor.Wait(key); 
    block = true; 
} 

// thread B 
lock (key) 
{ 
    block = false; 
    Monitor.Pulse(key); 
} 

作者說ThreadC將得到_locker第一,但在我的演示中,我跑三個線程我發現這是不真正。 ThreadC最後得到_locker。

這裏是我的代碼:

class Program 
{ 
    private static readonly object _locker = new object(); 
    public static void ThreadA() 
    { 

     new Thread(() => 
     { 
      lock (_locker) 
      { 
       Console.WriteLine("Thread A acquire lock then wait threadId {0}", Thread.CurrentThread.ManagedThreadId); 
       Monitor.Wait(_locker); 
       Thread.Sleep(1000); 
       Console.WriteLine("Thread A Continue .. threadId {0}", Thread.CurrentThread.ManagedThreadId); 
      } 
     }).Start(); 

    } 
    public static void ThreadB() 
    { 

     new Thread(() => 
     { 
      lock (_locker) 
      { 
       Console.WriteLine("Thread B acquire lock...then pulse threadId {0}",Thread.CurrentThread.ManagedThreadId); 
       Monitor.Pulse(_locker); 
       Thread.Sleep(1000); 
       Console.WriteLine("Thread B sleep...then realse the locker threadId {0}", Thread.CurrentThread.ManagedThreadId); 
      } 
     }).Start(); 

    } 

    public static void ThreadC() 
    { 

     new Thread(ThreadA).Start(); 
    } 

    static void Main(string[] args) 
    { 


     ThreadA(); 

     Thread.Sleep(10); // ensure threadA get _locker firstly 

     ThreadB(); 

     ThreadC(); 


    } 
} 
+0

顯示測試程序的輸出。 –

回答

1

作者說ThreadC將獲得_locker第一

我不認爲作者說,在所有。您發佈該帖寫道:

如果在就緒隊列中其他線程,他們將已發佈的

有一個很重要的「如果」在線程之前獲取鎖該聲明。您的代碼示例似乎並不能保證線程C會在線程A之前的就緒隊列中。事實上,憑​​借ThreadC()方法啓動一個全新的線程,該線程又會再次啓動線程ThreadA(),線程C甚至更有可能被推遲。

當然,線程執行完全取決於Windows線程調度程序。除非添加同步(例如在調用ThreadB()之前有10毫秒的延遲),否則線程的執行順序取決於線程調度程序的意願。雖然典型的線程會按計劃循環,但不能保證這一點。

但是,即使我們假設保證了循環線程調度,我也沒有看到代碼中的任何內容會需要甚至建議線程C在線程A之前進入監視器的就緒隊列中,而不介意你引用的作者的陳述應該是這樣的。

在評論,你問:

有沒有可能threadC首先得?

隨着您發佈的代碼,這沒有真正的機會發生,因爲線程C必須在嘗試獲取鎖之前通過兩個線程啓動。線程B未能首先獲得鎖定並釋放線程A的機率很小。這在理論上是可行的,但我懷疑你在實踐中是否曾經看到過它。即使沒有線程C是兩個線程,似乎也不太可能(儘管至少更合理)。

也就是說,考慮到線程C只是試圖獲取鎖,並且永遠不會進入等待隊列,您可以演示線程A開始等待後線程C到達監視器就緒隊列的場景,但在線程A再次準備就緒之前,因此允許線程C在線程A再次獲取之前獲取該鎖。

我修改了原始代碼以顯示此內容,包括向輸出添加「經過秒數」(以便更容易看到什麼時候發生),刪除啓動線程C的「包裝器」線程以及添加參數幫助控制和識別線程:

using System.Diagnostics; 
using System.Threading; 
using static System.Console; 

namespace TestSO46334766MonitorQueues 
{ 
    class Program 
    { 
     private static readonly object _locker = new object(); 
     public static void ThreadA(string label, int delay, bool wait) 
     { 
      new Thread(() => 
      { 
       LogMessage($"Thread {label} started, threadId {Thread.CurrentThread.ManagedThreadId}"); 
       Thread.Sleep(delay); 
       LogMessage($"Thread {label} attempt to acquire lock, threadId {Thread.CurrentThread.ManagedThreadId}"); 
       lock (_locker) 
       { 
        LogMessage($"Thread {label} acquire lock{(wait ? " then wait" : "")} threadId {Thread.CurrentThread.ManagedThreadId}"); 
        if (wait) Monitor.Wait(_locker); 
        Thread.Sleep(1000); 
        LogMessage($"Thread {label} continue .. threadId {Thread.CurrentThread.ManagedThreadId}"); 
       } 
      }).Start(); 
     } 

     public static void ThreadB() 
     { 
      new Thread(() => 
      { 
       LogMessage($"Thread B attempt to acquire lock, threadId {Thread.CurrentThread.ManagedThreadId}"); 
       lock (_locker) 
       { 
        LogMessage($"Thread B acquire lock...sleep, then pulse threadId {Thread.CurrentThread.ManagedThreadId}"); 
        Thread.Sleep(500); 
        Monitor.Pulse(_locker); 
        LogMessage($"Thread B sleep...then release the locker threadId {Thread.CurrentThread.ManagedThreadId}"); 
        Thread.Sleep(1000); 
       } 
      }).Start(); 
     } 

     public static void ThreadC() 
     { 
      //new Thread(() => ThreadA("C", 250, false)).Start(); 
      ThreadA("C", 250, false); 
     } 

     static void Main(string[] args) 
     { 
      ThreadA("A", 0, true); 
      Thread.Sleep(10); // ensure threadA get _locker firstly 
      ThreadB(); 
      ThreadC(); 
      ReadLine(); 
     } 

     static Stopwatch sw = Stopwatch.StartNew(); 

     static void LogMessage(string message) 
     { 
      WriteLine($"{sw.Elapsed.TotalSeconds:0.0}: {message}"); 
     } 
    } 
} 

下面是輸出的一個例子:

0.1: Thread A started, threadId 3 
0.1: Thread A attempt to acquire lock, threadId 3 
0.1: Thread A acquire lock then wait threadId 3 
0.2: Thread C started, threadId 5 
0.2: Thread B attempt to acquire lock, threadId 4 
0.2: Thread B acquire lock...sleep, then pulse threadId 4 
0.4: Thread C attempt to acquire lock, threadId 5 
0.7: Thread B sleep...then release the locker threadId 4 
1.7: Thread C acquire lock threadId 5 
2.7: Thread C continue .. threadId 5 
3.7: Thread A continue .. threadId 3

注意在上面線程C-實際上得到線程B運行之前,在具有儘管線程B的首先被創造。這是Windows線程調度程序如何不保證線程執行順序的一個例子。

還要注意的是,雖然線程C與線程B同時啓動,但它所做的第一件事是在實際嘗試獲取鎖之前進行休眠。這使得線程B有機會首先獲得鎖。因此,當線程C嘗試獲取鎖定時,它不能。但它確實得到放入就緒隊列。線程A仍然在等待隊列中,所以當線程B最終釋放它時(通過調用Pulse()),線程A在線程後面調用。一旦線程B最終離開鎖,就緒隊列中的線程就會按順序獲得鎖,導致它們實際上也按順序運行。

+0

我真的很感激你可以回答這個問題, 我只是想知道哪個線程在ThreadB調用Monitor.Pulse()時會得到_locker,如果在ThreadB()之後添加Thread.sleep(100)會怎麼樣? 確保ThreadC可以在就緒隊列中; –

+0

threadC有沒有先得到的機會? 或 它不能推測哪個線程可以首先得到_locker –

+0

_「有沒有任何機會threadC首先得到?」_ - 不,不是真的......不是因爲你引入了10ms的延遲。如果你擺脫了這種延遲,那麼線程C可能會首先獲得鎖,從而成爲等待隊列中的第一個線程。請注意,由於A和C都使用「Wait()」,所以它們首先根據哪個調用Wait()進行排序。如果C簡單地試圖獲得鎖,即使實際上稍後調度,它也可能在A之前結束,因爲A被卡在等待隊列中,直到B調用「Pulse()」。 –