Tengo la siguiente clase.
class Test{
public HashSet<string> Data = new HashSet<string>();
}
Necesito cambiar el campo "Datos" de diferentes subprocesos, por lo que me gustaría obtener algunas opiniones sobre mi implementación actual segura para subprocesos.
class Test{
public HashSet<string> Data = new HashSet<string>();
public void Add(string Val){
lock(Data) Data.Add(Val);
}
public void Remove(string Val){
lock(Data) Data.Remove(Val);
}
}
¿Existe una mejor solución, ir directamente al campo y protegerlo del acceso concurrente por múltiples hilos?
System.Collections.Concurrent
ReaderWriterLock
será útil (eficiente) cuando haya varios lectores y un solo escritor. Tenemos que saber si este es el caso de OPRespuestas:
Su implementación es correcta. Desafortunadamente, .NET Framework no proporciona un tipo de hashset concurrente incorporado. Sin embargo, hay algunas soluciones alternativas.
ConcurrentDictionary (recomendado)
El primero es usar la clase
ConcurrentDictionary<TKey, TValue>
en el espacio de nombresSystem.Collections.Concurrent
. En el caso, el valor no tiene sentido, por lo que podemos usar un simplebyte
(1 byte en memoria).Esta es la opción recomendada porque el tipo es seguro para subprocesos y le brinda las mismas ventajas que una
HashSet<T>
clave, excepto que el valor son objetos diferentes.Fuente: Social MSDN
Bolsa concurrente
Si no le importan las entradas duplicadas, puede usar la clase
ConcurrentBag<T>
en el mismo espacio de nombres de la clase anterior.Auto-implementación
Finalmente, como lo hizo, puede implementar su propio tipo de datos, utilizando el bloqueo u otras formas en que .NET le brinda seguridad para subprocesos. Aquí hay un gran ejemplo: Cómo implementar ConcurrentHashSet en .Net
El único inconveniente de esta solución es que el tipo
HashSet<T>
no tiene acceso oficialmente concurrente, incluso para operaciones de lectura.Cito el código de la publicación vinculada (originalmente escrita por Ben Mosher ).
EDITAR: Mueva los métodos de bloqueo de entrada fuera de los
try
bloques, ya que podrían lanzar una excepción y ejecutar las instrucciones contenidas en losfinally
bloques.fuente
null
referencia (la referencia necesita 4 bytes en un tiempo de ejecución de 32 bits y 8 bytes en un tiempo de ejecución de 64 bits). Por lo tanto, el uso de abyte
, una estructura vacía o similar puede reducir la huella de la memoria (o no si el tiempo de ejecución alinea los datos en los límites de la memoria nativa para un acceso más rápido).En lugar de envolver
ConcurrentDictionary
o bloquear unaHashSet
, creé un realConcurrentHashSet
basado enConcurrentDictionary
.Esta implementación admite operaciones básicas por elemento sin
HashSet
las operaciones establecidas, ya que tienen menos sentido en escenarios concurrentes IMO:Salida: 2
Puede obtenerlo de NuGet aquí y ver la fuente en GitHub aquí .
fuente
ISet<T>
interfaz bo que realmente coincida con laHashSet<T>
semántica?Overlaps
por ejemplo, necesitaría bloquear la instancia a lo largo de su ejecución o proporcionar una respuesta que ya puede estar equivocada. Ambas opciones son IMO malas (y los consumidores pueden agregarlas externamente).Como nadie más lo mencionó, ofreceré un enfoque alternativo que puede o no ser apropiado para su propósito particular:
Colecciones inmutables de Microsoft
De una publicación de blog del equipo de MS detrás:
Estas colecciones incluyen ImmutableHashSet <T> e ImmutableList <T> .
Actuación
Dado que las colecciones inmutables utilizan estructuras de datos de árbol debajo para permitir el intercambio estructural, sus características de rendimiento son diferentes de las colecciones mutables. Cuando se compara con una colección mutable de bloqueo, los resultados dependerán de la contención de bloqueo y los patrones de acceso. Sin embargo, tomado de otra publicación de blog sobre las colecciones inmutables:
En otras palabras, en muchos casos la diferencia no será notable y debe elegir la opción más simple, que sería para conjuntos concurrentes
ImmutableHashSet<T>
, ¡ya que no tiene una implementación mutable de bloqueo existente! :-)fuente
ImmutableHashSet<T>
no ayuda mucho si su intención es actualizar el estado compartido de varios subprocesos o me falta algo aquí?ImmutableInterlocked.Update
Parece ser el eslabón perdido. ¡Gracias!La parte difícil de hacer un
ISet<T>
concurrente es que los métodos establecidos (unión, intersección, diferencia) son de naturaleza iterativa. Como mínimo, debe iterar sobre todos los n miembros de uno de los conjuntos involucrados en la operación, mientras bloquea ambos conjuntos.Pierde las ventajas de un
ConcurrentDictionary<T,byte>
cuando tiene que bloquear todo el conjunto durante la iteración. Sin bloqueo, estas operaciones no son seguras para subprocesos.Dada la sobrecarga adicional de
ConcurrentDictionary<T,byte>
, probablemente sea más sabio usar el peso más livianoHashSet<T>
y simplemente rodear todo en las cerraduras.Si no necesita las operaciones de configuración, use
ConcurrentDictionary<T,byte>
y solo usedefault(byte)
como valor cuando agregue claves.fuente
Prefiero soluciones completas, así que hice esto: tenga en cuenta que mi cuenta se implementa de una manera diferente porque no veo por qué uno debería tener prohibido leer el hashset al intentar contar sus valores.
@ Zen, gracias por empezar.
fuente
EnterWriteLock
, ¿por quéEnterReadLock
existe? ¿No se puede usar el bloqueo de lectura para métodos comoContains
?