Pregunta

Tengo un Dictionary < string, someobject > .

EDITAR: Se me señaló que mi ejemplo era malo. Toda mi intención no era actualizar las referencias en un bucle, sino actualizar diferentes valores basados ??en diferentes subprocesos para actualizar / obtener los datos. Cambié el ciclo a un método.

Necesito actualizar elementos en mi diccionario: una clave a la vez y me preguntaba si hay algún problema al usar el bloqueo en el valor .key de mi objeto Diccionario.

private static Dictionary<string, MatrixElement> matrixElements = new Dictionary<string, MatrixElement>();

//Pseudo-code

public static void UpdateValue(string key)
{
    KeyValuePair<string, MatrixElement> keyValuePair = matrixElements[key];
    lock (keyValuePair.Key)
    {
        keyValuePair.Value  = SomeMeanMethod();
    }
}

¿Eso esperaría en la corte o fallaría? Solo quiero que cada valor en el diccionario se bloquee de forma independiente para que bloquear (y actualizar) un valor no bloquee los otros. También sé que el bloqueo se mantendrá durante mucho tiempo, pero los datos no serán válidos hasta que se actualicen por completo.

¿Fue útil?

Solución

Bloquear un objeto al que se puede acceder fuera del código de bloqueo es un gran riesgo. Si algún otro código (en cualquier lugar) bloquea ese objeto, podría encontrarse con algunos puntos muertos que son difíciles de depurar. También tenga en cuenta que bloquea el objeto , no la referencia, por lo que si le doy un diccionario, aún puedo tener referencias a las teclas y bloquearlas, lo que hace que bloqueemos el mismo objeto.

Si encapsula completamente el diccionario y genera las claves usted mismo (nunca se pasan, entonces puede estar seguro.

Sin embargo, trate de apegarse a una regla: limite la visibilidad de los objetos que bloquea al código de bloqueo siempre que sea posible.

Por eso ves esto:

public class Something
{
  private readonly object lockObj = new object();

  public SomethingReentrant()
  {
    lock(lockObj)    // Line A
    {
      // ...
     }
   }
}

en lugar de ver la línea A anterior reemplazada por

  lock(this)

De esa manera, un objeto separado está bloqueado y la visibilidad es limitada.

Editar Jon Skeet observó correctamente que lockObj anterior debería ser de solo lectura.

Otros consejos

No, esto no funcionaría.

La razón es internamiento de cadenas . Esto significa que:

string a = "Something";
string b = "Something";
¡

son ambos el mismo objeto! Por lo tanto, nunca debe bloquear las cadenas porque si alguna otra parte del programa (por ejemplo, otra instancia de este mismo objeto) también quiere bloquear la misma cadena, podría crear accidentalmente una contención de bloqueo donde no sea necesario; posiblemente incluso un punto muerto.

Sin embargo, siéntase libre de hacer esto sin cadenas. Para mayor claridad, hago un hábito personal crear siempre un objeto de bloqueo separado:

class Something
{
    bool threadSafeBool = true;
    object threadSafeBoolLock = new object(); // Always lock this to use threadSafeBool
}

Te recomiendo que hagas lo mismo. Cree un diccionario con los objetos de bloqueo para cada celda de la matriz. Luego, bloquee estos objetos cuando sea necesario.

PS. Cambiar la colección sobre la que estás iterando no se considera muy agradable. Incluso arrojará una excepción con la mayoría de los tipos de colección. Intenta refactorizar esto, p. iterar sobre una lista de claves, si siempre será constante, no los pares.

Nota: supongo una excepción cuando la modificación de la colección durante la iteración ya está arreglada

El diccionario no es una colección segura para subprocesos, lo que significa que no es seguro modificar y leer la colección de diferentes subprocesos sin sincronización externa. Hashtable es (was?) Seguro para subprocesos para el escenario de un escritor y muchos lectores, pero Dictionary tiene una estructura de datos interna diferente y no hereda esta garantía.

Esto significa que no puede modificar su diccionario mientras accede a él para leer o escribir desde el otro hilo, simplemente puede romper estructuras internas de datos. Bloquear la clave no protege la estructura de datos interna, porque mientras modifica esa clave, alguien podría estar leyendo una clave diferente de su diccionario en otro hilo. Incluso si puede garantizar que todas sus claves son los mismos objetos (como se dijo sobre el internamiento de cadenas), esto no lo pone del lado seguro. Ejemplo:

  1. Bloqueas la llave y comienzas a modificar el diccionario
  2. Otro hilo intenta obtener valor para la clave que cae en el mismo depósito que la bloqueada. Esto no es solo cuando los códigos hash de dos objetos son iguales, sino más frecuentemente cuando el código hash% tableSize es el mismo.
  3. Ambos hilos acceden al mismo depósito (lista vinculada de claves con el mismo valor de hashcode% tableSize)

Si no existe dicha clave en el diccionario, el primer hilo comenzará a modificar la lista, y el segundo hilo probablemente leerá un estado incompleto.

Si dicha clave ya existe, los detalles de implementación del diccionario aún podrían modificar la estructura de datos, por ejemplo, mover las claves a las que se accedió recientemente al encabezado de la lista para una recuperación más rápida. No puede confiar en los detalles de implementación.

Hay muchos casos como ese, cuando habrá dañado el diccionario. Por lo tanto, debe tener un objeto de sincronización externo (o usar el Diccionario en sí mismo, si no está expuesto al público) y bloquearlo durante toda la operación. Si necesita bloqueos más granulares cuando la operación puede llevar mucho tiempo, puede copiar las claves que necesita actualizar, iterar sobre ellas, bloquear todo el diccionario durante la actualización de una sola clave (no olvide verificar que la clave todavía está allí) y liberarla en Deje correr otros hilos.

Si no me equivoco, la intención original era bloquear un solo elemento, en lugar de bloquear todo el diccionario (como bloqueo de nivel de tabla vs. bloqueo de nivel de fila en una base de datos)

no puedes bloquear la clave del diccionario como se explica aquí.

Lo que puede hacer es mantener un diccionario interno de objetos de bloqueo, que corresponde al diccionario real. Entonces, cuando desee escribir en YourDictionary [Key1], primero bloqueará InternalLocksDictionary [Key1], por lo que solo un hilo se escribirá en YourDictionary.

un ejemplo (no demasiado limpio) puede ser encontrado aquí .

Acabo de encontrar esto y pensé en compartir un código que escribí hace unos años en el que necesitaba un diccionario sobre una base clave

 using (var lockObject = new Lock(hashedCacheID))
 {
    var lockedKey = lockObject.GetLock();
    //now do something with the dictionary
 }

la clase de bloqueo

class Lock : IDisposable
    {
        private static readonly Dictionary<string, string> Lockedkeys = new Dictionary<string, string>();

        private static readonly object CritialLock = new object();

        private readonly string _key;
        private bool _isLocked;

        public Lock(string key)
        {
            _key = key;

            lock (CritialLock)
            {
                //if the dictionary doesnt contain the key add it
                if (!Lockedkeys.ContainsKey(key))
                {
                    Lockedkeys.Add(key, String.Copy(key)); //enusre that the two objects have different references
                }
            }
        }

        public string GetLock()
        {
            var key = Lockedkeys[_key];

            if (!_isLocked)
            {
                Monitor.Enter(key);
            }
            _isLocked = true;

            return key;
        }

        public void Dispose()
        {
            var key = Lockedkeys[_key];

            if (_isLocked)
            {
                Monitor.Exit(key);
            }
            _isLocked = false;
        }
    }

¡En tu ejemplo, no puedes hacer lo que quieres hacer!

Obtendrá una System.InvalidOperationException con un mensaje de Se modificó la colección; la operación de enumeración puede no ejecutarse.

Aquí hay un ejemplo para probar:

using System.Collections.Generic;
using System;

public class Test
{
    private Int32 age = 42;

    static public void Main()
    {
       (new Test()).TestMethod();
    }

    public void TestMethod()
    {
        Dictionary<Int32, string> myDict = new Dictionary<Int32, string>();

        myDict[age] = age.ToString();

        foreach(KeyValuePair<Int32, string> pair in myDict)
        {
            Console.WriteLine("{0} : {1}", pair.Key, pair.Value);
            ++age;
            Console.WriteLine("{0} : {1}", pair.Key, pair.Value);
            myDict[pair.Key] = "new";
            Console.WriteLine("Changed!");
        }
    }   
}

El resultado sería:

42 : 42
42 : 42

Unhandled Exception: System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
   at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
   at System.Collections.Generic.Dictionary`2.Enumerator.MoveNext()
   at Test.TestMethod()
   at Test.Main()

Puedo ver algunos problemas potenciales allí:

  1. las cadenas se pueden compartir, por lo que no necesariamente sabe quién más podría estar bloqueando ese objeto clave por qué otro motivo
  2. las cadenas podrían no compartirse: puede estar bloqueando una tecla de cadena con el valor " Key1 " y alguna otra pieza de código puede tener un objeto de cadena diferente que también contenga los caracteres "Clave1". Para el diccionario son la misma clave, pero en lo que respecta al bloqueo son objetos diferentes.
  3. Ese bloqueo no evitará cambios en los objetos de valor, es decir, matrixElements[someKeyfont>.ChangeAllYourContents()
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top