2013-12-12 3 views
13

ここでは、スタックオーバーフローの私は、単一の引数関数をmemoizes foundコードをしました:メモ化機能は、同時に複数のスレッドから呼び出されたときスレッドセーフな関数のメモをC#でどのように実行するのですか?

static Func<A, R> Memoize<A, R>(this Func<A, R> f) 
{ 
    var d = new Dictionary<A, R>(); 
    return a=> 
    { 
     R r; 
     if (!d.TryGetValue(a, out r)) 
     { 
      r = f(a); 
      d.Add(a, r); 
     } 
     return r; 
    }; 
} 

このコードは私のためにその仕事をしていませんが、それは時々失敗します。 Addメソッドは同じ引数で2回呼び出され、例外がスローされます。

どのようにしてメモをスレッドセーフにすることができますか?それは同時に複数のスレッドから呼び出すことができますので、fはスレッドセーフそのものである必要があり

static Func<A, R> ThreadsafeMemoize<A, R>(this Func<A, R> f) 
{ 
    var cache = new ConcurrentDictionary<A, R>(); 

    return argument => cache.GetOrAdd(argument, f); 
} 

機能:

答えて

18

あなたが必要なすべてをしているConcurrentDictionary.GetOrAddを使用することができます。

このコードでは、一意の引数の値に1回だけ関数fが呼び出されることを保証するものではありません。実際には、忙しい環境で何度も呼び出すことができます。この種の契約が必要な場合は、related questionの答えを見てください。ただし、コンパクトではなく、ロックを使用する必要があることに注意してください。

+5

'GetOrAdd'は、指定された引数に対してfが複数回呼び出されることを完全に防止しないことに注意してください。呼び出しのちょうど* 1 *の結果が辞書に追加されることを保証します。キャッシュされた値が追加される前に、スレッドが同時にキャッシュをチェックするイベントでは、複数の呼び出しを取得できます。このことについて気にすることはしばしばありませんが、呼び出しに望ましくない副作用がある場合には、それについて言及します。 –

+0

@JamesWorldはい、そうです。それを反映するように編集された答えは、ありがとう! – Gman

+1

私はちょっと混乱しています - ローカル変数の 'cache'はここにありませんか? 'ThreadsafeMemoize()'が呼び出されるたびに、新しい辞書は作成されませんか? – dashnick

2

Gmanが述べたように、ConcurrentDictionaryがこれを行うための好ましい方法ですが、単純なlockステートメントでは利用できない場合はこれで十分です。代わりにConcurrentDictionaryのロックを使用して

static Func<A, R> Memoize<A, R>(this Func<A, R> f) 
{ 
    var d = new Dictionary<A, R>(); 
    return a=> 
    { 
     R r; 
     lock(d) 
     { 
      if (!d.TryGetValue(a, out r)) 
      { 
       r = f(a); 
       d.Add(a, r); 
      } 
     } 
     return r; 
    }; 
} 

一つの潜在的な問題は、この方法は、あなたのプログラムにしてデッドロックを導入する可能性があります。

  1. 次の2つのメモ化機能_memo1_memo2はインスタンス変数です_memo1 = Func1.Memoize()_memo2 = Func2.Memoize()を、持っています。
  2. スレッド1は_memo1Func1は処理を開始します。
  3. Thread2は_memo2を呼び出し、Func2の内部には_memo1とThread2ブロックが呼び出されます。
  4. スレッド1の処理Func1は、関数の後半で_memo2の呼び出しになります。
  5. DEADLOCK!
だから、全く可能であれば

ConcurrentDictionaryを使用しますが、あなたは、あなたがロックを使用できない場合は代わりにあなたがメモ化内部関数で実行されているか、あなたが開く機能の外にスコープされ、他のメモ化の関数を呼び出すことはありません(_memo1_memo2がデッドロックが起こっていないインスタンス変数の代わりにローカル変数であった場合)

(注、パフォーマンスが少しReaderWriterLockを使用することによって改善することができるが、あなたはまだ同じデッドロックの問題があります。)

1

にSystem.Collectionsを使用しました。ジェネリック;

Dictionary<string, string> _description = new Dictionary<string, string>(); 
public float getDescription(string value) 
{ 
    string lookup; 
    if (_description.TryGetValue (id, out lookup)) { 
     return lookup; 
    } 

    _description[id] = value; 
    return lookup; 
} 
関連する問題