2013-12-12 14 views
13

Qui su stack overflow ho found il codice che memoizes funzioni singolo argomento:Come eseguire la memoizzazione della funzione thread-safe in 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; 
    }; 
} 

Anche se questo codice fa il suo lavoro per me, non riesce a volte quando la funzione memoized viene chiamata dai più thread contemporaneamente: il metodo Add viene chiamato due volte con lo stesso argomento e genera un'eccezione.

Come posso rendere la memoizzazione thread-safe?

risposta

18

È possibile utilizzare ConcurrentDictionary.GetOrAdd che fa tutto il necessario:

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

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

La funzione f dovrebbe essere thread-safe per sé, perché può essere chiamato da più thread contemporaneamente.

Questo codice inoltre non garantisce che la funzione f sia chiamata una sola volta per valore di argomento univoco. Può essere chiamato molte volte, infatti, nell'ambiente occupato. Se hai bisogno di questo tipo di contratto, dovresti dare un'occhiata alle risposte in questo related question, ma tieni presente che non sono così compatti e richiedono l'uso di blocchi.

+5

Nota che 'GetOrAdd' non impedirà completamente che f venga chiamato più di una volta per un determinato argomento; garantisce solo che il risultato di solo * uno * delle invocazioni venga aggiunto al dizionario. È possibile ottenere più di una chiamata nel caso in cui i thread controllino la cache contemporaneamente prima che il valore memorizzato nella cache sia stato aggiunto. Spesso non vale la pena preoccuparsi di questo, ma lo menziono nel caso in cui l'invocazione abbia effetti collaterali indesiderati. –

+0

@JamesWorld Sì, è vero. Risposta modificata per riflettere questo, grazie! – Gman

+1

Sono un po 'confuso - non è 'cache' qui una variabile locale? Ogni volta che viene chiamato 'ThreadsafeMemoize()', non creerà un nuovo dizionario? – dashnick

2

Come Gman menzionato ConcurrentDictionary è il metodo preferito per eseguire questa operazione, tuttavia se ciò non è disponibile per una semplice istruzione lock è sufficiente.

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

Un potenziale problema con serrature invece di ConcurrentDictionary è questo metodo potrebbe introdurre deadlock al tuo programma.

  1. Si hanno due funzioni memoized _memo1 = Func1.Memoize() e _memo2 = Func2.Memoize(), dove _memo1 e _memo2 sono variabili di istanza.
  2. Thread1 chiama _memo1, Func1 inizia l'elaborazione.
  3. Thread2 chiama _memo2, all'interno di Func2 c'è una chiamata a _memo1 e blocchi di Discussione2.
  4. L'elaborazione di Thread1 di Func1 arriva a una chiamata di _memo2 in ritardo nella funzione, blocchi Thread1.
  5. DEADLOCK!

Quindi, se possibile, utilizzare ConcurrentDictionary, ma se non si può e si utilizza serrature invece non chiamare altre funzioni Memoized che hanno un ambito al di fuori della funzione che si esegue quando all'interno Memoized funzioni o si apre fino al rischio di deadlock (se _memo1 e _memo2 sono state variabili locali anziché variabili di istanza, il deadlock non si sarebbe verificato).

(Nota, la prestazione può essere leggermente migliorata usando ReaderWriterLock ma è ancora avrà lo stesso problema di stallo.)

1

using System.Collections.Generico;

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