2010-11-20 10 views
5

Saltando de this thread, estoy tratando de utilizar ConcurrentDictionary para replicar el siguiente:.NET4.0: Actualización Hilo de seguridad de ConcurrentDictionary <TKey, TValue>

public static class Tracker 
{ 
    private static Dictionary<string, int> foo = new Dictionary<string, int>(); 
    private static object myLock = new object(); 

    public static void Add(string bar) 
    { 
     lock(myLock) 
     { 
      if (!foo.ContainsKey(bar)) 
       foo.Add(bar, 0); 

      foo[bar] = foo[bar] + 1; 
     } 
    } 


    public static void Remove(string bar) 
    { 
     lock(myLock) 
     { 
      if (foo.ContainsKey(bar)) 
      { 
       if (foo[bar] > 0) 
        foo[bar] = foo[bar] - 1; 
      } 
     } 
    } 
} 

Mi intento inicial es:

public static class Tracker2 
{ 
    private static ConcurrentDictionary<string, int> foo = 
     new ConcurrentDictionary<string, int>(); 

    public static void Add(string bar) 
    { 
     foo.AddOrUpdate(bar, 1, (key, n) => n + 1); 
    }  

    public static void Remove(string bar) 
    { 
     // Adding a 0'd item may be ok if it wasn't there for some reason, 
     // but it's not identical to the above Remove() implementation. 
     foo.AddOrUpdate(bar, 0, (key, n) => (n > 0) ? n - 1 : 0); 
    } 
} 

¿Es este uso correcto? Voy a evitar situaciones tales como:

  1. Tema 1: Añadir llamadas ("a"), foo [ "a"] es ahora 1.
  2. Tema 1 se intercambia por Tema 2.
  3. Tema 2: llamadas Eliminar ("a"), foo ["a"] ahora 0.
  4. El hilo 2 se intercambia por el hilo 1.
  5. Tema 1: Solicita foo ["a"] y asume que el valor es 1 pero en realidad es 0.

Respuesta

1

ElEl métodono es equivalente: su original agregará primero 0 en foo[bar] si no existe nada allí, luego incremente, con un resultado total de 1 en foo[bar]. El segundo agregará un 0 si no existe nada allí, y solo realizará el incremento en llamadas posteriores.

Remove El método no es equivalente: el original no hará nada si es foo.ContainsKey(bar)false, mientras que el segundo añadirá un valor de 0 para esa tecla.

¿Leyó la documentación para AddOrUpdate?


EDIT: después de que ha editado para cuidar de los problemas anteriores, la respuesta a su pregunta es más claramente "no, no va a resolver sus problemas." El motivo es el método Remove: su funcionamiento ahora no es atómico, ya que hay dos operaciones atómicas separadas: TryGetValue y foo[bar] = currValue - 1. El otro hilo podría entrar entre esas operaciones y causar las inconsistencias que le preocupan.

El objetivo de los métodos como AddOrUpdate o GetOrAdd es realizar operaciones comunes tan atómicas como sea posible. Desafortunadamente, parece que no han atomicizado su caso con ConcurrentDictionary; no hay UpdateIfExists.

Sin embargo, estoy bastante seguro de que la siguiente va a resolver sus problemas: the Interlocked.Decrement method. Esto resuelve el siguiente caso:

  1. Eliminar se invoca en el subproceso 1; foo[bar] tiene el valor 2, que se almacena en currValue.
  2. Subproceso 2 toma el control y se llama allí Add; foo[bar] se incrementa a 3.
  3. Volver al subproceso 1, que establece foo[bar] en el valor currValue - 1 = 1.

Traté de pensar en otros casos que podrían romperse, pero no pudieron ... lo que podría significar que no soy tan bueno como los otros comentaristas que dicen lo contrario: P.

EDIT 2: I pensado en un problema con el uso Interlocked.Decrement: que no sólo decremento si su valor es positivo :(

+0

Buen punto. Editado para reflejar la equivalencia con la implementación del diccionario. – Bullines

+0

¿Qué son 's_ConcurrentRequests' y' url'? También actualicé mi respuesta para señalar una inequidad con el método 'Agregar 'también. – Domenic

+0

Interlocked.Decrement podría estar bien, pero tendría que asegurarme de que los valores sean mayores que 0. Esto podría estar bien, porque los valores nunca deberían ser menores que cero. Pero el truco será tener una forma segura de verificar que el valor sea aún mayor que cero. Quizás Interlock.Exchange? – Bullines

3

No se puede evitar las inconsistencias que están preocupados por sólo mediante el uso de un ConcurrentDictionary. .Necesita algo mucho más fuerte y robusto para garantizar eso. Pregúntese si realmente necesita ese nivel de consistencia antes de embarcarse en la solución del problema. Los dragones estarán allí.

Repitiendo mi error de manera diferente: ConcurrentDictionary solo aseguran múltiples los hilos que golpean el diccionario no lo estropearán. No garantiza nada acerca de la consistencia de los valores cuando se extraen correctamente. vely por múltiples hilos. No puede evitar que te dispares en el pie.

+0

En esta situación, ¿qué tipo de enfoque me permitiría mantener mi pie en un estado sin disparo? :) – Bullines

+0

Básicamente necesita un mecanismo de adquisición/liberación. – jason

+0

¿Se puede usar Mutex.WaitOne/Mutex.ReleaseMutex antes/después de las operaciones en Agregar() y Eliminar() para solucionar esto? – Bullines

1

Respuesta corta: No, el concurrentdictionary no protegerá contra la secuencia que describe.

Pero, de nuevo, tampoco lo hará su código anterior.

Si necesita una garantía de que un objeto en la colección se mantendrá durante la duración de una serie de operaciones en un hilo, asigne el objeto a una variable local en el hilo y use solo la variable local. Entonces a su código de subproceso no le importa lo que le sucede al objeto en la colección.

Cuestiones relacionadas