2013-06-25 9 views
7

ConcurrentDictionary Pitfall - Are delegates factories from GetOrAdd and AddOrUpdate synchronized? note che AddOrUpdate non è atomico (e non può garantire che i delegati non vengano eseguiti più di una volta).atomic addorupdate (cercando di scrivere l'armadietto denominato usando il dizionario simultaneo)

Sto cercando di implementare un nome di bloccaggio implementazione utilizzando un dizionario concomitante alla here, ma dove il dizionario non dovrebbe crescere per sempre, come questo:

public class ConcurrentDictionaryNamedLocker : INamedLocker 
{ 
    // the IntObject values serve as the locks and the counter for how many RunWithLock jobs 
    // are about to enter or have entered the critical section. 
    private readonly ConcurrentDictionary<string, IntObject> _lockDict = new ConcurrentDictionary<string, IntObject>(); 
    private static readonly IntObject One = new IntObject(1); 
    private readonly Func<string, IntObject, IntObject> _decrementFunc = (s, o) => o - 1; 
    private readonly Func<string, IntObject, IntObject> _incrementFunc = (s, o) => o + 1; 
    private readonly Func<string, IntObject> _oneFunc = s => new IntObject(1); 
    private readonly Func<string, IntObject> _zeroFunc = s => new IntObject(0); 

    public TResult RunWithLock<TResult>(string name, Func<TResult> body) 
    { 
     name = name.ToLower(); 
     TResult toReturn; 
     lock (_lockDict.AddOrUpdate(name, _oneFunc, _incrementFunc)) 
     { 
      toReturn = body(); 
      if (!_lockDict.TryRemove(name, One)) 
       _lockDict.AddOrUpdate(name, _zeroFunc, _decrementFunc); 
     } 
     return toReturn; 
    } 

    public void RunWithLock(string name, Action body) 
    { 
     name = name.ToLower(); 
     lock (_lockDict.AddOrUpdate(name, _oneFunc, _incrementFunc)) 
     { 
      body(); 
      if (!_lockDict.TryRemove(name, One)) 
       _lockDict.AddOrUpdate(name, _zeroFunc, _decrementFunc); 
     } 
    } 
} 

Ma il problema è l'AddOrUpdate non è atomica , quindi vedo che spesso le voci non vengono rimosse in caso di contesa. Sono abbastanza sicuro che se AddOrUpdate fosse atomico, il codice sopra farebbe il suo lavoro e le voci verrebbero rimosse in modo appropriato.

Notare l'utilizzo della rimozione condizionale mediante il metodo di estensione chiave + val TryRemove (chiave, val) menzionato here. Inoltre, IntObject è un semplice wrapper oggetto mutabile di un int.

Quali sono le mie opzioni? Esistono implementazioni di dizionari simultanei che hanno 1. rimozioni atomiche (su chiave e valore) e 2. AddOrUpdate è atomico e garantisce che i delegati non vengano eseguiti più di una volta?

Hai altre idee? Mi piacerebbe che l'armadietto denominato fosse veloce ma non avesse problemi di pressione di memoria dato un namespace di lock illimitato ma con non molta contesa su un nome dato. Per quanto ne so, lo string interning locking by name cresce per sempre e non viene mai ripulito e ha altri effetti collaterali. E i mutex sono semi-lenti e hanno vari fastidi (limite di 260 caratteri).

+0

È è possibile utilizzare un dizionario digitato come "ConcurrentDictionary >" come descritto in questa risposta http://stackoverflow.com/a/12611341/1236734 –

risposta

1

Sarebbe utile vedere il codice esatto per IntValue poiché questo è il codice da eseguire nel delegato AddOrUpdate.

Credo che il problema è che il codice si aspetta IntValue istanze siano entrambi:

  • Mutevole per bloccare un singolo IntValue istanza associato a ciascuna stringa (nonostante il conteggio di riferimento incrementando successivamente)
  • Immutable in modo che il confronto IntValue s ad una statica One opere come i criteri di rimozione

Se cambio il codice in modo che IntValue supporta uno zero immutabile questo sembra funzionare:

private readonly Func<string, IntObject> _zeroFunc = s => IntObject.Zero; 

...

public void RunWithLock(string name, Action body) 
{ 
    name = name.ToLower(); 
    lock (_lockDict.AddOrUpdate(name, _oneFunc, _incrementFunc)) 
    { 
     body(); 
     _lockDict.AddOrUpdate(name, _zeroFunc, _decrementFunc); 
     _lockDict.TryRemove(name, IntObject.Zero); 
    } 
} 

ma ho scelto di attuare le IntValue metodi come questo e come non-operatori:

internal IntObject Dec(int p) 
    { 
     var newVal = Interlocked.Decrement(ref value); 
     if (newVal == 0) return Zero; 
     return this; 
    } 
Problemi correlati