2012-08-28 32 views
1

我有以下代碼來緩存我在多線程應用程序中使用的並行字典中某些類的實例。在此ConcurrentDictionary緩存場景中鎖定必需

簡單地說,當我用id參數傳遞類時,它首先檢查具有給定標識的私有類的實例是否存在於字典中,如果不創建私有類的實例(這需要很長時間,的秒),並將其添加到字典供將來使用。

public class SomeClass 
{ 
    private static readonly ConcurrentDictionary<int, PrivateClass> SomeClasses = 
     new ConcurrentDictionary<int, PrivateClass>(); 

    private readonly PrivateClass _privateClass; 

    public SomeClass(int cachedInstanceId) 
    { 
     if (!SomeClasses.TryGetValue(cachedInstanceId, out _privateClass)) 
     { 
      _privateClass = new PrivateClass(); // This takes long time 
      SomeClasses.TryAdd(cachedInstanceId, _privateClass); 
     } 
    } 

    public int SomeCalculationResult() 
    { 
     return _privateClass.CalculateSomething(); 
    } 

    private class PrivateClass 
    { 
     internal PrivateClass() 
     { 
      // this takes long time 
     } 

     internal int CalculateSomething() 
     { 
      // Calculates and returns something 
     } 
    } 
} 

我的問題是,我需要增加一個鎖周圍的外部類的構造函數的產生和分配部分以使此代碼線程安全的還是不錯的,因爲它是什麼?

更新:

後SLaks的suggestion,試圖用ConcurrentDictionary的GetOrAdd()法的Lazy的組合,但遺憾的是PrivateClass的構造還是調用一次以上。測試代碼見https://gist.github.com/3500955

更新2: 您可以在這裏看到最終的解決方案: https://gist.github.com/3501446

+0

解決方案得到了它:https://gist.github.com/3501446 –

回答

6

你濫用ConcurrentDictionary

在多線程代碼中,您不應該檢查項目的存在,如果它不存在,則添加它。
如果兩個線程同時運行該代碼,它們最終都會添加它。

一般來說,這種問題有兩種解決方案。你可以把所有的代碼封裝在一個鎖中,或者你可以在一次原子操作中重新設計它。

ConcurrentDictionary被設計用於這種場景。

你應該簡單地調用

_privateClass = SomeClasses.GetOrAdd(cachedInstanceId, key => new PrivateClass()); 
+0

有了這個用法,會發生哪些正在試圖獲得的價值的其他線程什麼,而'PrivateClass'的構造函數正在執行?他們會等待物品被添加嗎? –

+1

@MennanKara http://ayende.com/blog/4802/concurrentdictionary-getoradd-may-call-the-valuefactory-method-more-than-once http://msdn.microsoft.com/en-us/library/ ee378677.aspx – SLaks

+2

'GetOrAdd'不是原子的。請參閱http://msdn.microsoft.com/en-us/library/dd997369.aspx –

3

鎖定是沒有必要的,但你正在做的是不是線程安全的。不必先檢查字典是否存在某個項目,然後根據需要添加它,則應該使用ConcurrentDictionary.GetOrAdd()在一次原子操作中完成所有操作。

否則,您將暴露自己與使用常規字典時遇到的相同問題:另一個線程可能會在插入之前檢查是否存在,然後再添加條目至SomeClasses

2

使用ConcurrentDictionary和懶惰<牛逼>在https://gist.github.com/3500955你的代碼示例是不正確的 - 你寫:

private static readonly ConcurrentDictionary<int, PrivateClass> SomeClasses = 
     new ConcurrentDictionary<int, PrivateClass>(); 
    public SomeClass(int cachedInstanceId) 
    { 
     _privateClass = SomeClasses.GetOrAdd(cachedInstanceId, (key) => new Lazy<PrivateClass>(() => new PrivateClass(key)).Value); 
    } 

..這應該是:

private static readonly ConcurrentDictionary<int, Lazy<PrivateClass>> SomeClasses = 
     new ConcurrentDictionary<int, Lazy<PrivateClass>>(); 
    public SomeClass(int cachedInstanceId) 
    { 
     _privateClass = SomeClasses.GetOrAdd(cachedInstanceId, (key) => new Lazy<PrivateClass>(() => new PrivateClass(key))).Value; 
    } 

您需要使用ConcurrentDictionary < TKey,懶惰<TVal> >,而不是ConcurrentDictionary < TKey,TVal >。 關鍵是你只能在之後訪問Lazy 的值,從GetOrAdd()返回正確的Lazy對象 - 將Lazy對象的Value值發送給GetOrAdd函數會破壞使用它的全部目的。

編輯: - 你丫在https://gist.github.com/mennankara/3501446 :)