2011-01-14 8 views
10

En el contexto de esta declaración,Aclaración de lectura y escritura en un C# Diccionario

Un diccionario puede apoyar varios lectores simultáneamente, siempre y que no se modifica la colección. Aún así, enumerar a través de una colección no es intrínsecamente un procedimiento thread-safe. En el raro caso donde una enumeración sostiene con accesos de escritura, la colección debe estar bloqueada durante la enumeración completa . Para permitir el acceso a la colección por medio de múltiples hilos para leer y escribir, debe implementar su propia sincronización.

¿Qué significa leer y escribir? Tengo entendido que una lectura es una operación que busca una clave y proporciona una referencia a su valor y una escritura es una operación que agrega o elimina un par de valores clave del diccionario. Sin embargo, no puedo encontrar nada concluyente con respecto a esto.

Entonces, la gran pregunta es, al implementar un diccionario seguro para hilos, ¿una operación que actualiza el valor de una clave existente en el diccionario se considera un lector o escritor? Planeo tener varios subprocesos accediendo a claves únicas en un diccionario y modificando sus valores, pero los hilos no agregarán/eliminarán nuevas claves.

La implicación obvia, suponiendo que modificar un valor existente no es una operación de escritura en el diccionario, es que mi implementación de la dicción segura de subprocesos puede ser mucho más eficiente, ya que no necesitaría un bloqueo exclusivo cada vez Intento actualizar el valor de una clave existente.

El uso de ConcurrentDictionary desde .Net 4.0 no es una opción.

Respuesta

2

sobrescribir un valor existente debe ser tratada como una operación de escritura

+2

Esto es realmente un comentario, no una respuesta a la pregunta. Utilice "agregar comentario" para dejar comentarios al autor. – SliverNinja

+0

@SliverNinja, me parece que esta respuesta se dirige a la pregunta central "¿una operación que actualiza el valor de una clave existente en el diccionario se considera un lector o escritor?". – binki

-1

Modificación de un valor es una escritura e introduce una condición de carrera.

Digamos que el valor original de mydict [5] = 42. Una actualizaciones de rosca mydict [5] a ser 112. Otro hilo actualizaciones mydict [5] a ser 837.

¿Qué debe el valor de mydict [5] estar al final? El orden de los hilos es importante en este caso, lo que significa que o bien debe asegurarse de que el pedido sea explícito o de que no se escriba.

+1

Lo que está describiendo no es realmente una condición de carrera, ya que de todas formas, quien gane lo último gana, y eso sería lo mismo si cerrara el acceso. Una condición de carrera sería algo así como: if (! Dict.ContainsKey (5)) dict (5, new Value()) ahora hay una posible incoherencia generada. – Neil

+0

@Neil Creo que es una condición de carrera, simplemente no es una condición de carrera que el diccionario pueda manejar. Ya codifico en el lugar para garantizar que el tipo de condición de carrera mencionado en la respuesta no ocurra. – Joshua

+3

@Joshua: una condición de carrera podría ocurrir dentro del código del diccionario que implemente 'mydict [5] = X' pero no exista una condición de carrera entre las 3 instrucciones de conjunto de Serinus. Quien alguna vez establezca el valor de las últimas ganancias, esa es solo una descripción de la programación normal. Se produce una condición de carrera en el caso en el que establece el estado en función del examen previo previo del estado. Por sí solo x = 1 no puede generar una condición de carrera x ++ puede. – Neil

0

Cualquier cosa que pueda afectar los resultados de otra lectura debe considerarse una escritura.

cambiando una clave es el más definitivamente una escritura, ya que hará que el elemento se mueva en el hash interna o índice o embargo diccionarios hacen su O (n) log() cosas ...

Lo que es posible que desee que hacer es mirar a ReaderWriterLock

http://msdn.microsoft.com/en-us/library/system.threading.readerwriterlock.aspx

+2

Mucha de la documentación que había leído mostraba que el bloqueo de Reader/Writer tiene un rendimiento sorprendentemente pobre, peor que el simple uso de un solo candado. Parece que ReaderWriterLockSlim debe ser lo que uses si está disponible o utilizas algo más. http://msdn.microsoft.com/en-us/library/system.threading.readerwriterlockslim.aspx –

+0

@Erik -Estoy de acuerdo, pero agregaría: siempre es un equilibrio. Tendría que depender de los otros costos en la mezcla. Si la cantidad de lectura y escritura es aproximadamente igual, aconsejaría simplemente usar lock(), pero si la escritura es rara, y generalmente lo es, entonces el bloqueo del lector de lector probablemente sea correcto. Será fácil pasar al bloqueo delgado cuando se actualicen. Hacer un perfil cuando tienes un problema es la mejor manera de obtener rendimiento. Muy a menudo las personas pasan días codificando para ahorrar milisegundos por operación. – Neil

+0

De acuerdo, a veces los desarrolladores gastan demasiado tratando de preoptimizar. Utilicé la frase "rendimiento sorprendentemente pobre" para denotar que parece no ser un problema de rendimiento sutil sino más bien drástico. Por lo general, se considera mejor evitarlo en lugar de usarlo. Al menos, eso parece ser una gran cantidad de conclusiones que veo alcanzadas durante varias discusiones de rendimiento. –

0

actualización de un valor es conceptualmente una operación de escritura. Al actualizar un valor con acceso concurrente donde se realiza una lectura antes de que se complete una escritura, se lee un valor anterior. Cuando dos escriben conflicto, se puede almacenar el valor incorrecto.

Agregar un nuevo valor podría desencadenar un crecimiento del almacenamiento subyacente. En este caso, se asigna memoria nueva, todos los elementos se copian en la memoria nueva, se agrega el nuevo elemento, el objeto de diccionario se actualiza para hacer referencia a la nueva ubicación de memoria para almacenamiento y la memoria anterior se libera y está disponible para recolección de basura. Durante este tiempo, más escrituras podrían causar un gran problema. Dos escrituras al mismo tiempo podrían desencadenar dos instancias de esta copia de memoria. Si sigue la lógica, verá que se perderá un elemento, ya que solo el último hilo para actualizar la referencia sabrá sobre los elementos existentes y no sobre los otros elementos que intentaban agregarse.

ICollection provides a member to synchronize access y la referencia sigue siendo válida en operaciones de crecimiento/contracción.

+0

Este es un punto interesante, si declaro un diccionario de e inserto ("Clave1", "Hola") luego hago el directorio ["Clave1"] = "Esta es una cuerda realmente larga", tiene que volver a asignar memoria por valor, o dado que el valor es un objeto, y la memoria ya ha sido asignada para el objeto, ¿podría volver a usar esa memoria? Supongo que mucho dependería de las entrañas del objeto Diccionario. – Joshua

+0

Para el diccionario el almacenamiento subyacente es ICollection >. El hecho de que ya haya insertado un valor significa que se agregó un registro a ICollection (ampliando su contenido, aumentando la memoria si es necesario). El contenido es solo una estructura que contiene una referencia a dos objetos de cadena. Cambiar la cadena de valor del diccionario a otra cosa simplemente crea una nueva cadena en la memoria y actualiza la referencia de KeyValuePair. Sin cambios en el tamaño de almacenamiento subyacente IDictionary. –

+0

Además, Insertará una excepción si intenta insertar la misma clave dos veces. Esto puede suceder fácilmente en un entorno de subprocesos múltiples donde el bloqueo no se implementó correctamente. Si desea estar más seguro, solo acceder a una clave inexistente hará que se cree. Si existe, se actualizará. No hay excepción lanzando. Use Insertar si agregar la misma clave es una condición de error real que desea capturar. directory.Insert ("Key1", "Hello"); versus directorio ["Key1"] = "Hola"; –

0

Una operación de lectura es cualquier cosa que obtiene una clave o valor de Dictionary, una operación de escritura es cualquier cosa que actualiza o agrega una clave o un valor. Por lo tanto, un proceso de actualización de una clave se consideraría como un escritor.

Una forma sencilla de hacer un diccionario segura hilo es crear su propia implementación de IDictionary que simplemente bloquea un mutex y luego reenvía la llamada a una aplicación:

public class MyThreadSafeDictionary<T, J> : IDictionary<T, J> 
{ 
     private object mutex = new object(); 
     private IDictionary<T, J> impl; 

     public MyThreadSafeDictionary(IDictionary<T, J> impl) 
     { 
      this.impl = impl; 
     } 

     public void Add(T key, J value) 
     { 
     lock(mutex) { 
      impl.Add(key, value); 
     } 
     } 

     // implement the other methods as for Add 
} 

Se podría reemplazar el mutex con un lector - Bloqueo del escritor: si tiene algunos hilos, solo lee el diccionario.

También tenga en cuenta que los objetos Dictionary no admiten el cambio de claves; La única forma segura de lograr lo que desea es eliminar el par clave/valor existente y agregar uno nuevo con la clave actualizada.

+0

¿hay alguna razón para hacer esto en lugar de usar ConcurrentDictionary? – mcmillab

3

Un punto importante sin embargo, no se menciona es que si TValue es un tipo de clase, las cosas en poder de un Dictionary<TKey,TValue> serán las identidades de TValue objetos. Si uno recibe una referencia del diccionario, el diccionario no conocerá ni le importará nada de lo que pueda hacer con el objeto al que se refiere.

Una pequeña clase de servicio útil en los casos en todas las claves asociadas con un diccionario se conocerá antes del código que necesita utilizar es:

class MutableValueHolder<T> 
{ 
    public T Value; 
} 

Si uno quiere tener multi-hilo de código cuente cuántas veces aparecen varias cadenas en un grupo de archivos, y uno sabe de antemano todas las cadenas de interés, entonces puede usar algo como Dictionary<string, MutableValueHolder<int>> para ese fin. Una vez que el diccionario se carga con todas las cadenas adecuadas y una instancia MutableValueHolder<int> para cada una, cualquier cantidad de subprocesos puede recuperar referencias a objetos MutableValueHolder<int> y utilizar Threading.Interlocked.Increment u otros métodos para modificar el Value asociado a cada uno, sin tener que escribir al diccionario en absoluto.

Cuestiones relacionadas