2010-10-11 24 views
4

我想知道是否有任何缺點來鎖定集合,如List<T>,HashSet<T>Dictionary<TKey, TValue>而不是簡單的object鎖定集合與syncRoot的任何缺點?

注:在下面的例子,那就是鎖發生的唯一的地方,它沒有被從多個地方鎖定,但靜態方法可以從多個線程調用。此外,_dict永遠不會在GetSomething方法之外訪問。

我當前的代碼如下所示:

private static readonly Dictionary<string, string> _dict = new Dictionary<string, string>(); 
public static string GetSomething(string key) 
{ 
    string result; 
    if (!_dict.TryGetValue(key, out result)) 
    { 
     lock (_dict) 
     { 
      if (!_dict.TryGetValue(key, out result)) 
      { 
       _dict[key] = result = CalculateSomethingExpensive(key); 
      } 
     } 
    } 
    return result; 
} 

另一名開發者告訴我,鎖定在一個集合會導致一些問題,但我懷疑。如果我這樣做,我的代碼會更高效嗎?

private static readonly Dictionary<string, string> _dict = new Dictionary<string, string>(); 
private static readonly object _syncRoot = new object(); 
public static string GetSomething(string key) 
{ 
    string result; 
    if (!_dict.TryGetValue(key, out result)) 
    { 
     lock (_syncRoot) 
     { 
      if (!_dict.TryGetValue(key, out result)) 
      { 
       _dict[key] = result = CalculateSomethingExpensive(key); 
      } 
     } 
    } 
    return result; 
} 
+0

您是否考慮將_dict和GetSomething()移入單獨的類中?它顯然做了一些不同於班裏其他人的事情。 (它看起來像一個Memoization模式) – Sjoerd 2010-10-11 23:52:16

+0

你必須聲明_dict是不穩定的,纔有機會做到正確。另請參閱有關Double-Checked Locking及其缺陷的此答案:http://stackoverflow.com/questions/394898/double-checked-locking-in-net/394932#394932 – Sjoerd 2010-10-11 23:55:40

+0

@Sjoerd volatile不需要用C#double - 在許多情況下檢查。然而,這裏的雙重檢查不會有效,因爲它不是雙重檢查的字段讀取,而是雙重檢查的方法調用,只有當調用的方法是原子的時候才起作用,這不是,所以經過了雙重檢查在這裏顯然是錯誤的。 – 2010-10-12 01:50:53

回答

12

如果你暴露你收藏到外面的世界,那麼,是的,這可能是一個問題。通常的建議是鎖定你獨有的東西,並且永遠不會被你的影響之外的代碼意外鎖定。這就是爲什麼通常最好鎖定一些你永遠不會考慮公開的內容(即爲此目的創建的特定鎖對象)。這樣,當你的記憶失敗時,你會 永不 可能不會得到意想不到的結果。

要更直接地回答您的問題:在混合中添加另一個對象永遠不會更高效,但在一些被認爲但未被測量的效率之前放置通常被認爲是良好編碼實踐的東西可能是過早發生的選擇。我贊成最佳做法,直到它明顯造成瓶頸。

+0

事實上,當發生這種情況時,死鎖風險會增加很多! – 2010-10-11 23:30:01

+0

正如我所說的,該集合從不暴露,只能通過單一方法訪問。 – ckknight 2010-10-11 23:30:56

+0

只要你肯定是這樣,那麼沒有問題。因爲我很愚蠢而且健忘,所以我總是喜歡使用專門用於鎖定的對象。在一個充滿危險的多線程世界中擔心的是少一件事。 – spender 2010-10-11 23:34:40

3

號只要變量不是從其他地方訪問,並且可以保證鎖只有在這裏使用的,沒有缺點。實際上,Monitor.Enter(這是C#使用的lock)的文檔就是這樣做的。

然而,作爲一般規則,我還是建議使用專用的對象鎖定。這通常更安全,並且如果您將此對象暴露給任何其他代碼,將會保護您,因爲您不會打開對象被其他代碼鎖定的可能性。

1

直接回答你的問題:沒有

這沒有什麼區別任何對象你鎖定到。 .NET只關心它的引用,它完全像一個指針一樣工作。考慮將.NET鎖定爲一個大的同步散列表,其中鍵是對象引用,值是一個布爾值,表示您可以輸入或不輸入顯示器。如果兩個線程鎖定到不同的對象(一!= B)鎖的,他們可以進入監控的同時,即使a.Equals(B)(這是非常重要的!)。但是,如果他們鎖定a和b,並且(a == b),則一次只有其中一個將在監視器中。

只要字典是不是你的範圍之內訪問你有沒有性能影響。如果dict在其他地方可見,其他用戶代碼可能會獲得鎖定,即使不一定需要(認爲你的deskmate是愚蠢的並且鎖定了他在代碼中找到的第一個隨機對象)。

希望能一直幫助。

0

我會推薦使用ICollection。SyncRoot上的對象鎖定,而不是自己的對象:

private static readonly Dictionary<String, String> _dict = new Dictionary<String, String>(); 
    private static readonly Object _syncRoot = ((ICollection)_dict).SyncRoot; 
+2

僅支持SyncRoot,因爲它必須是不打破ICollection接口的定義。它被廣泛描述爲一個錯誤,這就是爲什麼它不包含在ICollection 中。現有的使用它的代碼應該重構爲不再依賴於它。新代碼當然不應該使用它。 – 2010-10-11 23:46:11

+1

感謝Jon,我沒有聽說過這個 – 2010-10-12 00:49:31

+0

http://blogs.msdn.com/b/brada/archive/2003/09/28/50391.aspx就是這樣。 – 2010-10-12 01:54:30

6

在這種情況下,我會在收集鎖;鎖的目的直接關係到集合而不是任何其他對象,所以在使用它作爲鎖對象時存在一定程度的自我註釋。

雖然我會做出改變。

在文檔中沒有什麼可以說TryGetValue是線程安全的,如果在字典處於無效狀態時調用它,將不會引發異常(或更糟糕的情況),因爲它是通過添加新的價值。因爲它不是原子的,所以這裏使用的雙重讀取模式(以避免花費時間獲取鎖)是不安全的。這將不得不改爲:

private static readonly Dictionary<string, string> _dict = new Dictionary<string, string>(); 
public static string GetSomething(string key) 
{ 
    string result; 
    lock (_dict) 
    { 
     if (!_dict.TryGetValue(key, out result)) 
     { 
      _dict[key] = result = CalculateSomethingExpensive(key); 
     } 
    } 
    return result; 
} 

如果它很可能涉及更多的成功比讀取失敗(即因此需要寫入),使用ReaderWriterLockSlim會提供更好的併發這些讀取。

編輯:我只是注意到,你的問題不是關於一般的偏好,而是關於效率。真的,在整個系統中使用4字節多內存的效率差異(因爲它是靜態的)絕對爲零。這個決定並不是關於效率,而是因爲兩者具有相同的技術價值(在這種情況下是)是關於你是否在集合上找到鎖定,或者在一個單獨的對象上更好地表達你對另一個開發者的意圖(包括你將來)。