Pregunta

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

Actualmente, utilizo un Dictionary para contener todos los usuarios que se accede constantemente por múltiples hilos (de un grupo de subprocesos, por lo que ninguna cantidad exacta de hilos), y que ha sincronizado el acceso.

Hace poco me enteré de que había un conjunto de colecciones compatibles con el proceso en .NET 4.0, y parece ser muy agradable. Me preguntaba, lo que sería el 'más eficiente y más fácil de manejar' opción, ya que tengo la opción entre tener un Dictionary normal con acceso sincronizado, o tener un ConcurrentDictionary que ya es seguro para subprocesos.

ConcurrentDictionary de referencia para .NET 4.0

¿Fue útil?

Solución

Una colección threadsafe vs. un no-threadsafe-colección puede ser considerado de una manera diferente.

Considere una tienda sin secretario, excepto al momento de pagar. Usted tiene un montón de problemas si las personas no actúan de manera responsable. Por ejemplo, digamos que un cliente toma una lata de una pirámide de lata, mientras que un empleado está construyendo la pirámide, todo el infierno se desatará. O, si lo llega a dos clientes para el mismo artículo, al mismo tiempo, ¿quién gana? ¿Habrá una pelea? Esto es un no-hilo-colección. Hay un montón de maneras de evitar problemas, pero todos ellos requieren algún tipo de bloqueo, o más bien el acceso explícita de alguna manera u otra.

Por otro lado, considere una tienda con un empleado en un escritorio, y sólo se puede comprar a través de él. Que se obtiene en línea, y le pide un elemento, que trae de nuevo a usted, y usted sale de la línea. Si necesita varios elementos, sólo se puede recoger tantos elementos en cada ida y vuelta que pueda recordar, pero hay que tener cuidado de evitar acaparando el empleado, esto ira de los otros clientes en línea detrás de usted.

Ahora considere esto. En la tienda con un empleado, lo que si usted consigue todo el camino hasta la parte delantera de la línea, y pida al secretario "¿Tiene usted algún papel higiénico", y dice "Sí", y luego vas "Ok, I' pondremos en contacto con usted cuando sé cuánto necesito", a continuación, en el momento en que está de vuelta en la parte delantera de la línea, la tienda puede por supuesto ser vendido. Este escenario no es impedido por una colección multi-hilo.

A threadsafe colección garantiza que sus estructuras de datos internos son válidos en todo momento, incluso si accede desde varios subprocesos.

Una colección de no-hilo no viene con ningún tipo de garantías. Por ejemplo, si añade algo a un árbol binario en un hilo, mientras que otro hilo está ocupado reequilibrar el árbol, no hay garantía se añadirá el artículo, o incluso que el árbol sigue siendo válido después, puede ser que sea corrupta allá de la esperanza.

A-hilo colección no obstante, garantiza que las operaciones secuenciales en el hilo de todo el trabajo en la misma "instantánea" de su estructura interna de datos, lo que significa que si usted tiene un código como este:

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

puede obtener una NullReferenceException porque tree.Count entre medio y tree.First(), otro hilo ha limpiado el resto de nodos en el árbol, lo que significa First() volverá null.

En este escenario, ya sea necesario para ver si la colección en cuestión tiene una forma segura de conseguir lo que quieres, tal vez lo que necesita para volver a escribir el código anterior, o puede que tenga que cerrar.

Otros consejos

Aún hay que tener mucho cuidado al usar colecciones compatibles con el proceso debido a flujos seguros no significa que usted puede pasar por alto todos los problemas de threads. Cuando una colección anuncia como seguro para subprocesos, por lo general significa que se mantiene en un estado constante, incluso cuando varios subprocesos son la lectura y la escritura al mismo tiempo. Pero eso no quiere decir que un solo hilo verá una secuencia "lógica" de los resultados si se llama a varios métodos.

Por ejemplo, si primero se comprueba si existe una clave y luego obtener el valor que corresponde a la tecla, esa llave, ya no existe, incluso con una versión ConcurrentDictionary (porque otro hilo podría haber retirado la llave). Todavía es necesario utilizar el bloqueo en este caso (o mejor: combinar las dos llamadas usando TryGetValue ).

Así que hacer uso de ellos, pero no creo que le da un pase libre para ignorar todos los problemas de concurrencia. Aún hay que tener cuidado.

Internamente ConcurrentDictionary utiliza un bloqueo separado para cada cubo hash. Como siempre y cuando utilice únicamente Añadir / TryGetValue y métodos similares que funcionan en las entradas individuales, el diccionario funcionará como una estructura de datos casi libre de bloqueo con el respectivo beneficio de rendimiento dulce. Otoh los métodos de enumeración (incluyendo la propiedad Count) bloquear todos los cubos a la vez y por lo tanto son peor que un diccionario sincronizada, en cuanto al rendimiento.

Yo diría, sólo tiene que utilizar ConcurrentDictionary.

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

¿Ha visto el reactiva Extensiones para .Net 3.5sp1. Según Jon Skeet, han portado un haz de las extensiones paralelas y estructuras de datos simultáneas para .Net3.5 sp1.

Hay un conjunto de muestras para .Net 4 Beta 2, que describe en detalle bastante bueno sobre cómo utilizarlos las extensiones paralelas.

He acabo de pasar la última semana probando el ConcurrentDictionary utilizando 32 hilos para realizar E / S. Parece que funciona como se anuncia, lo que indicaría una enorme cantidad de pruebas se ha puesto en él.

Editar :. .NET 4 ConcurrentDictionary y patrones

Microsoft ha lanzado un PDF llamado Patrones de Paralell programación. Su verdaderamente vale la pena descargar, ya que describe en detalles muy bonitos los patrones de la derecha de utilizar para .Net 4 extensiones concurrentes y los patrones contra evitar. Aquí está.

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

Hemos utilizado para la recolección de ConcurrentDictionary en caché, que es repoblada cada 1 hora y luego leídos por múltiples subprocesos de cliente, similar a la solución para el ¿Es este ejemplo de subprocesos? cuestión.

Hemos encontrado, que la cambia por ReadOnlyDictionary mejora el rendimiento general .

El ConcurrentDictionary es una gran opción si cumple todos los sus necesidades hilo de seguridad. Si no lo es, en otras palabras, de que están haciendo algo poco compleja, un Dictionary + normales lock pueden ser una mejor opción. Por ejemplo digamos que va a agregar algunas órdenes en un diccionario, y quiere mantener actualizado el importe total de las órdenes. Es posible escribir código como este:

private ConcurrentDictionary<int, Order> _dict;
private Decimal _totalAmount = 0;

while (await enumerator.MoveNextAsync())
{
    Order order = enumerator.Current;
    _dict.TryAdd(order.Id, order);
    _totalAmount += order.Amount;
}

Este código no es seguro para subprocesos. Múltiples hilos actualizando el campo _totalAmount pueden dejarlo en un estado corrupto. Por lo que puede tratar de protegerlo con una lock:

_dict.TryAdd(order.Id, order);
lock (_locker) _totalAmount += order.Amount;

Este código es "más seguro", pero todavía no es seguro para subprocesos. No hay garantía de que el _totalAmount es consistente con las entradas en el diccionario. Otro hilo puede tratar de leer estos valores, para actualizar un elemento de interfaz de usuario:

Decimal totalAmount;
lock (_locker) totalAmount = _totalAmount;
UpdateUI(_dict.Count, totalAmount);

El totalAmount puede no corresponder con el número de pedidos en el diccionario. Las estadísticas mostradas podrían estar equivocados. En este punto se dará cuenta de que hay que extender la protección lock para incluir la actualización del diccionario:

// Thread A
lock (_locker)
{
    _dict.TryAdd(order.Id, order);
    _totalAmount += order.Amount;
}

// Thread B
int ordersCount;
Decimal totalAmount;
lock (_locker)
{
    ordersCount = _dict.Count;
    totalAmount = _totalAmount;
}
UpdateUI(ordersCount, totalAmount);

Este código es perfectamente seguro, pero todos los beneficios de usar un ConcurrentDictionary se han ido.

  1. El rendimiento se ha convertido en peor que el uso de un Dictionary normal, debido a que el bloqueo interno dentro de la ConcurrentDictionary ahora es un desperdicio y redundante.
  2. Debe revisar todo el código para usos no protegidos de las variables compartidas.
  3. que hay que contentarse con el uso de una API incómoda (TryAdd ?, AddOrUpdate?).

Así que mi consejo es: empezar con una Dictionary + lock, y mantener la opción de actualizar más tarde a un ConcurrentDictionary como una optimización del rendimiento, si esta opción es realmente viable. En muchos casos no será.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top