2010-11-24 55 views
2

我有一個靜態System.Collections.Generic.Dictionary<K, V>用作緩存。在高速緩存未命中我做了一些昂貴的工作,以獲取指定鍵的值,並將其保存在緩存中:使用相同的鍵/值更新字典從多個線程安全嗎

private static Dictionary<K, V> _cache = ... 

public static V GetValue<K>(K key) 
{ 
    V value; 
    if (!_cache.TryGetValue(key, out value)) 
    { 
     value = GetExpensiveValue(key); 
     _cache[key] = value; // Thread-safety issue? 
    } 

    return value; 
} 

這是一個常見的模式。問題是:這是安全的還是在多線程嘗試同時調用GetValue()時會失敗?

假設GetExpensiveValue總是返回給定鍵的相同值,我並不關心哪個線程贏得競爭條件。是的,這意味着昂貴的操作可能會比需要的時間多一次,但這是一個折衷,如果讀取比寫入更頻繁,這可以讓我滿意,這可以讓我消除鎖定。但是,在字典裏面會出現什麼問題?也就是說,在某些情況下會拋出異常還是破壞其數據?我用一個簡單的控制檯應用測試了這一點,發現沒有問題,但當然,這並不意味着它將始終工作。

回答

1

這看起來不是線程安全的;字典內部可能已經處於被重寫的過程中。在最低它可能需要一個讀/寫鎖:

private readonly static ReaderWriterLockSlim @lock = new ReaderWriterLockSlim(); 
public static V GetValue(K key) 
{ 
    V value; 
    @lock.EnterReadLock(); 
    bool hasValue; 
    try 
    { 
     hasValue = _cache.TryGetValue(key, out value); 
    } 
    finally 
    { 
     @lock.ExitReadLock(); 
    } 

    if (!hasValue) 
    { 
     value = GetExpensiveValue(key); 
     @lock.EnterWriteLock(); 
     try 
     { 
      _cache[key] = value; 
     } 
     finally 
     { 
      @lock.ExitWriteLock(); 
     } 
    } 
    return value; 
} 

,如果你只想GetExpensiveValue(key)執行一次,將其移動寫鎖定,並添加一個雙重檢查:

 @lock.EnterWriteLock(); 
     try 
     { 
      if(!_cache.TryGetValue(key, out value)) { // double-check 
       value = GetExpensiveValue(key); 
       _cache[key] = value; 
      } 
     } 
     finally 
     { 
      @lock.ExitWriteLock(); 
     } 
0

如果你不關心比賽條件,那麼你應該沒問題。如果你關心的昂貴的操作,只是lockTryGetvalue(),然後調用TryGetValue()再次

private static Dictionary<K, V> _cache = ... 
private static object _lockObject = new object(); 

public static V GetValue<K>(K key) 
{ 
    V value; 
    if (!_cache.TryGetValue(key, out value)) 
    { 
     lock(_lockObject) 
     { 
      if (!_cache.TryGetValue(key, out value)) 
      { 
       value = GetExpensiveValue(key); 
       _cache[key] = value; // Thread-safety issue? 
      } 
     } 
    } 

    return value; 
} 

編輯: 考慮Marc的評論之後,我覺得上面的是不是最好的選擇。也許考慮使用ConcurrentDictionary來代替。

+3

這並不安全; TryGetValue *本身*不是線程安全的,並且如果字典正在被另一個線程突變,可能會在火花陣中爆炸 – 2010-11-24 06:28:59