.NET – Bloqueo de diccionario vs. ConcurrentDictionary

No pude encontrar suficiente información sobre los tipos ConcurrentDictionary , así que pensé en preguntar sobre esto aquí.

Actualmente, utilizo un Dictionary para contener a todos los usuarios a los que se accede constantemente por varios subprocesos (desde un grupo de subprocesos, por lo que no hay una cantidad exacta de subprocesos), y tiene acceso sincronizado.

Recientemente descubrí que había un conjunto de colecciones seguras para subprocesos en .NET 4.0, y parece ser muy agradable. Me preguntaba, ¿cuál sería la opción ‘más eficiente y fácil de administrar’, ya que tengo la opción entre tener un Dictionary normal con acceso sincronizado o tener un ConcurrentDictionary que ya es seguro para subprocesos?

Referencia al ConcurrentDictionary .NET 4.0

Una colección segura para hilos frente a una colección no segura para hilos se puede considerar de una manera diferente.

Considere una tienda sin dependiente, excepto al momento del pago. Tienes un montón de problemas si las personas no actúan responsablemente. Por ejemplo, digamos que un cliente toma una lata de una lata de pirámide mientras un empleado está construyendo la pirámide, todo el infierno se desataría. O bien, ¿qué pasa si dos clientes alcanzan el mismo artículo al mismo tiempo, quién gana? ¿Habrá una pelea? Esta es una colección sin hilos. Hay muchas maneras de evitar problemas, pero todos requieren algún tipo de locking, o acceso más bien explícito de una forma u otra.

Por otro lado, considere una tienda con un empleado en un escritorio, y solo puede comprar a través de él. Usted se pone en línea y le pide un artículo, se lo devuelve y se sale de la fila. Si necesita varios artículos, solo puede recoger tantos artículos en cada viaje de ida y vuelta como lo recuerde, pero debe tener cuidado para evitar acaparar al empleado, esto enojará a los otros clientes detrás de usted.

Ahora considera esto. En la tienda con un empleado, ¿qué sucede si llega hasta el frente de la fila y pregunta al empleado “¿Tiene papel higiénico?”, Y él responde “Sí”, y luego dice “Ok, yo”. Me pondré en contacto contigo cuando sepa cuánto necesito “, entonces, cuando vuelvas a estar al frente de la línea, la tienda puede agotarse. Esta situación no se previene con una colección segura.

Una colección de hilos seguros garantiza que sus estructuras internas de datos sean válidas en todo momento, incluso si se accede desde múltiples hilos.

Una colección que no sea segura para la fabricación de hilos no incluye ninguna de esas garantías. Por ejemplo, si agrega algo a un árbol binario en un hilo, mientras otro hilo está ocupado reequilibrando el árbol, no hay garantía de que el elemento se agregará, o incluso que el árbol sigue siendo válido después, podría estar dañado más allá de toda esperanza.

Sin embargo, una colección de hilos no garantiza que las operaciones secuenciales en el hilo funcionen en la misma “instantánea” de su estructura de datos interna, lo que significa que si tiene un código como este:

 if (tree.Count > 0) Debug.WriteLine(tree.First().ToString()); 

es posible que obtenga una NullReferenceException porque entre tree.Count y tree.First() , otro hilo ha eliminado los nodos restantes en el árbol, lo que significa que First() devolverá null .

Para este escenario, o necesita ver si la colección en cuestión tiene una forma segura de obtener lo que desea, quizás necesite volver a escribir el código anterior, o puede que necesite bloquearlo.

Todavía debe tener mucho cuidado al usar colecciones seguras para subprocesos porque la seguridad de subprocesos no significa que puede ignorar todos los problemas de subprocesamiento. Cuando una colección se anuncia a sí misma como segura para subprocesos, generalmente significa que permanece en un estado consistente incluso cuando varios hilos están leyendo y escribiendo simultáneamente. Pero eso no significa que un solo hilo verá una secuencia “lógica” de resultados si llama a múltiples métodos.

Por ejemplo, si primero comprueba si existe una clave y luego obtiene el valor que corresponde a la clave, esa clave puede que ya no exista incluso con una versión ConcurrentDictionary (porque otra secuencia podría haber eliminado la clave). Aún necesita usar el locking en este caso (o mejor: combine las dos llamadas usando TryGetValue ).

Así que utilícelos, pero no crea que le da un pase libre para ignorar todos los problemas de simultaneidad. Aún debes tener cuidado.

Internaly ConcurrentDictionary utiliza un locking por separado para cada cubo de hash. Siempre que use solo Add / TryGetValue y métodos similares que funcionen en entradas individuales, el diccionario funcionará como una estructura de datos casi sin lockings con el respectivo beneficio de rendimiento dulce. OTOH los métodos de enumeración (incluida la propiedad Count) bloquean todos los depósitos a la vez y, por lo tanto, son peores que un diccionario sincronizado, en términos de rendimiento.

Yo diría que solo use ConcurrentDictionary.

¿Has visto las extensiones reactivas para .Net 3.5sp1. Según Jon Skeet, han respaldado un paquete de extensiones paralelas y estructuras de datos concurrentes para .Net3.5 sp1.

Hay un conjunto de muestras para .Net 4 Beta 2, que describe con bastante detalle cómo usarlas las extensiones paralelas.

Acabo de pasar la última semana probando el ConcurrentDictionary usando 32 hilos para realizar E / S. Parece funcionar como se anuncia, lo que indicaría que se han realizado una gran cantidad de pruebas.

Edición : .NET 4 ConcurrentDictionary y patrones.

Microsoft ha lanzado un pdf llamado Progtwigción de Patrones de Paralell. Realmente vale la pena descargarlo, ya que describió con detalles realmente agradables los patrones correctos que se deben usar para las extensiones simultáneas de .NET 4 y los patrones anti a evitar. Aquí está.

Creo que el método ConcurrentDictionary.GetOrAdd es exactamente lo que la mayoría de los escenarios de múltiples subprocesos necesitan.

Básicamente, usted quiere ir con el nuevo ConcurrentDictionary. Nada más sacarlo de la caja, tiene que escribir menos código para hacer progtwigs seguros para subprocesos.

Hemos utilizado ConcurrentDictionary para la recolección en caché, que se vuelve a llenar cada 1 hora y luego se lee por varios subprocesos de cliente, similar a la solución para este tipo de archivos. ¿Es seguro este hilo de ejemplo? pregunta.

Encontramos que al cambiarlo a ReadOnlyDictionary se mejoró el rendimiento general.

Hay un buen curso sobre colecciones simultáneas en c #: colecciones concurrentes Pluralsight