2009-01-14 37 views
2

以下C#類用於多線程環境。我刪除了很多實際的代碼。幾乎同時調用MethodA和MethodB時會出現問題。 IsDepleted屬性中的鎖定順序不能解決問題。從IsDepleted屬性中刪除鎖(WaitingQueue)解決了死鎖問題,但是當另一個線程從WaitingQueue.Count == 0和Processing.Count == 0語句之間的WaitingQueue中添加/刪除一個項時,此解決方案會導致問題。如何在下面的C#代碼中防止死鎖?

using System.Collections.Generic; 

class Example 
{ 
    bool IsDepleted 
    { 
     get 
     { 
      lock (Processing) 
      { 
       lock (WaitingQueue) 
     { 
        return WaitingQueue.Count == 0 
      && Processing.Count == 0; 
     } 
      } 
     } 
    } 

    private readonly List<object> Processing = new List<object>(); 
    private readonly Queue<object> WaitingQueue = new Queue<object>(); 

    public void MethodA(object item) 
    { 
     lock (WaitingQueue) 
     { 
      if (WaitingQueue.Count > 0) 
      { 
       if (StartItem(WaitingQueue.Peek())) 
       { 
        WaitingQueue.Dequeue(); 
       } 
      } 
     } 
    } 

    public void MethodB(object identifier) 
    { 
     lock (Processing) 
     { 
      Processing.Remove(identifier); 
      if (!IsDepleted) 
      { 
       return; 
      } 
     } 
    //Do something... 
    } 

    bool StartItem(object item) 
    { 
     //Do something and return a value 
    } 
} 

回答

3

這取決於您是否想要快速修復或嚴格修復。

快速修復只是在所有情況下使用一個鎖對象。

例如private readonly object _lock = new object();

然後就鎖定了。但是,根據您的情況,這可能會影響性能,超出您的承受範圍。

I.e.您的代碼將變爲:

using System.Collections.Generic; 

class Example 
{ 
    private readonly object _lock = new object(); 

    bool IsDepleted 
    { 
     get 
     { 
      lock (_lock) 
      { 
       return WaitingQueue.Count == 0 
       && Processing.Count == 0; 
      } 
     } 
    } 

    private readonly List<object> Processing = new List<object>(); 
    private readonly Queue<object> WaitingQueue = new Queue<object>(); 

    public void MethodA(object item) 
    { 
     lock (_lock) 
     { 
      if (WaitingQueue.Count > 0) 
      { 
       if (StartItem(WaitingQueue.Peek())) 
       { 
        WaitingQueue.Dequeue(); 
       } 
      } 
     } 
    } 

    public void MethodB(object identifier) 
    { 
     lock (_lock) 
     { 
      Processing.Remove(identifier); 
      if (!IsDepleted) 
      { 
       return; 
      } 
     } 
     //Do something... 
    } 

    bool StartItem(object item) 
    { 
     //Do something and return a value 
    } 
} 
3

使方法A中的處理鎖和方法B中的WaitingQueue鎖(換句話說,使它看起來像第一個代碼塊)。這樣,你總是以相同的順序拿鎖,而且你永遠不會死鎖。

+0

在這種情況下,不需要有兩個鎖。 :) – 2009-01-14 19:00:51

+0

如果你打算這樣做,那麼你總是雙引號鎖定,你可能會把它簡化爲一個鎖 – JoshBerke 2009-01-14 19:01:01

+0

以相同的順序鎖定互斥鎖絕對是防止死鎖的必備工具。所需的鎖定順序並不意味着總是隻需要一個鎖。如果做得對,使用多個互斥體可以提高性能。 – gimpf 2009-01-14 19:48:06

2

簡化您的代碼並僅使用單個對象進行鎖定。

Monitor.TryEnter(加工,1000)

這會給你1秒超時:您還可以用替換你的鎖。所以本質上:

 if (Monitor.TryEnter(Processing, 1000)) 
     { 
      try 
      { 
       //do x 
      } 
      finally 
      { 
       Monitor.Exit(Processing); 
      } 
     } 

現在你不會停止死鎖,但你可以處理你沒有得到鎖的情況。