2010-11-24 19 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回以上実行されることを意味しますが、これはトレードオフであり、読み取りが書き込みよりも頻繁に行われると幸いです。しかし、実際に何かが辞書の中で間違っていますか?つまり、例外がスローされたり、データが破損することがあります。私はこれをシンプルなコンソールアプリで試してみましたが、問題は見つかりませんでしたが、もちろんそれが常に機能するというわけではありません。

答えて

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; 
} 

編集: マルクさんのコメントを考慮した後、私は上記の最良の選択肢ではないと思います。おそらくConcurrentDictionaryを代わりに使用することを検討してください。

+3

これは安全ではありません。 TryGetValue *自体はスレッドセーフではありません。辞書が別のスレッドによって突然変異されている場合、スパークのシャワーで爆発する可能性があります。 –

+2

良い点マーク。おそらくSystem.Collections.Concurrent.ConcurrentDictionary <>()を使うだけの方がいいだろうか? http://msdn.microsoft.com/en-us/library/dd287191.aspx –

関連する問題