14

Mi equipo está debatiendo actualmente este problema.Bloqueo comprobado doble en el diccionario "ContainsKey"

El código en cuestión es algo a lo largo de las líneas de

if (!myDictionary.ContainsKey(key)) 
{ 
    lock (_SyncObject) 
    { 
     if (!myDictionary.ContainsKey(key)) 
     { 
      myDictionary.Add(key,value); 
     } 
    } 
} 

Algunos de los mensajes que he visto dicen que esto puede ser un gran no no (cuando se utiliza TryGetValue). Sin embargo, los miembros de nuestro equipo dicen que está bien, ya que "ContainsKey" no itera en la colección de claves, pero verifica si la clave está contenida mediante el código hash en O (1). Por lo tanto, afirman que no hay peligro aquí.

Me gustaría obtener sus opiniones honestas sobre este tema.

+0

es posible que desee retirar ConcurrentDictionary. – chillitom

+1

Solo un detalle pero probablemente quiera decir '! ContainsKey()' –

+0

¿Ha encontrado problemas de eficiencia al bloquear todo el diccionario? – Nick

Respuesta

24

No haga esto. No es seguro.

Puede llamar a ContainsKey desde un hilo, mientras que otro hilo llama a Add. Eso simplemente no es compatible con Dictionary<TKey, TValue>. Si Add necesita reasignar cubos, etc., puedo imaginar que podría obtener resultados muy extraños, o una excepción. Es puede se han escrito de tal manera que no se ve ningún efecto desagradable, pero no me gustaría confiar en él.

Una cosa es el uso de una doble comprobación de bloqueo para simples lecturas/escrituras a un campo, aunque todavía diría en contra de ella - es otra para realizar llamadas a una API que ha sido descrito explícitamente como no siendo seguro para múltiples llamadas concurrentes.

Si estás en .NET 4, ConcurrentDictionary es probablemente el camino a seguir. De lo contrario, simplemente bloquee cada acceso.

+0

¿Qué sucede si se accede a este código mucho dentro de la aplicación? Entonces eso podría ser un problema de rendimiento. ¿Alguna otra forma de hacer esto además de ConcurrentDictionary? – JarJarrr

+1

@Amir: Prefiero sufrir una pequeña pérdida de rendimiento que hacer explotar la aplicación. ¿Ha perfilado la aplicación para ver si esto está causando un problema de rendimiento * real *? Los bloqueos no impugnados son baratos: no intentes rodar tu propio código sin bloqueo sin probar que realmente lo necesitas. –

+0

¿Es correcto suponer que si voy con un candado de acceso en lugar de DCL entonces tendré que bloquear cada acceso en cada método a este diccionario? – JarJarrr

6

Si se encuentra en un entorno multiproceso, puede preferir utilizar un ConcurrentDictionary. Blogged sobre él hace un par de meses, es posible encontrar el artículo útil: http://colinmackay.co.uk/blog/2011/03/24/parallelisation-in-net-4-0-the-concurrent-dictionary/

+0

¿Cuál es una amplia solución a este problema si el uso de .Net 4.0 no es una opción? (usando 3.5) – JarJarrr

+1

Buena alternativa y artículo pero en realidad no responde si el bloqueo propuesto es "OK". –

5

Este código es incorrecto. El tipo Dictionary<TKey, TValue> no admite operaciones simultáneas de lectura y escritura. Aunque se llama al método Add dentro del bloqueo, no es ContainsKey. Por lo tanto, permite fácilmente una violación de la regla de lectura/escritura simultánea y dará lugar a daños en su instancia

1

No parece seguro para subprocesos, pero probablemente sería difícil hacerlo fallar.

El argumento iteración vs hash lookup no se cumple, podría haber una colisión hash, por ejemplo.

0

Si este diccionario rara vez se escribe y se lee con frecuencia, a menudo empleo el doble bloqueo seguro reemplazando todo el diccionario en la escritura. Esto es particularmente efectivo si puede escribir por lotes juntos para que sean menos frecuentes.

Por ejemplo, esta es una versión reducida de un método que usamos que intenta obtener un objeto de esquema asociado con un tipo, y si no puede, entonces sigue adelante y crea objetos de esquema para todos los tipos que se encuentra en el mismo conjunto como el tipo especificado para minimizar el número de veces que el diccionario entero tiene que ser copiado:

public static Schema GetSchema(Type type) 
    { 
     if (_schemaLookup.TryGetValue(type, out Schema schema)) 
      return schema; 

     lock (_syncRoot) { 
      if (_schemaLookup.TryGetValue(type, out schema)) 
       return schema; 

      var newLookup = new Dictionary<Type, Schema>(_schemaLookup); 

      foreach (var t in type.Assembly.GetTypes()) { 
       var newSchema = new Schema(t); 
       newLookup.Add(t, newSchema); 
      } 

      _schemaLookup = newLookup; 

      return _schemaLookup[type]; 
     } 
    } 

Así el diccionario, en este caso se reconstruirán, como máximo, tantas veces como hay asambleas con tipos que necesitan esquemas. Durante el resto de la vida útil de la aplicación, los accesos al diccionario estarán libres de bloqueos.La copia del diccionario se convierte en un costo de inicialización por única vez del ensamblaje. El intercambio de diccionario es seguro para subprocesos porque las escrituras del puntero son atómicas, por lo que toda la referencia se conmuta a la vez.

Puede aplicar principios similares en otras situaciones también.

Cuestiones relacionadas