net mvc funciona framework ejemplo conjuntos concepto como asp c# multithreading thread-safety locking mutex

c# - mvc - Concurrente HashSet<T> en.NET Framework?



hashset entity framework (5)

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); } }

¿Hay una mejor solución para ir directamente al campo y protegerlo del acceso simultáneo por varios hilos?


Dado que 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 del blog del equipo de MS detrás de:

Si bien crear y ejecutar al mismo tiempo es más fácil que nunca, uno de los problemas fundamentales aún existe: el estado compartido mutable. La lectura de múltiples hilos suele ser muy fácil, pero una vez que el estado necesita ser actualizado, se vuelve mucho más difícil, especialmente en diseños que requieren bloqueo.

Una alternativa al bloqueo es hacer uso del estado inmutable. Se garantiza que las estructuras de datos inmutables nunca cambian y, por lo tanto, pueden transmitirse libremente entre diferentes hilos sin preocuparse de pisar los dedos de otra persona.

Sin embargo, este diseño crea un nuevo problema: ¿cómo se gestionan los cambios de estado sin copiar todo el estado cada vez? Esto es especialmente complicado cuando se trata de colecciones.

Aquí es donde entran colecciones inmutables

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 del bloqueo y los patrones de acceso. Sin embargo, tomado de otra publicación del blog sobre las colecciones inmutables:

P: He oído que las colecciones inmutables son lentas. ¿Son estos diferentes? ¿Puedo usarlos cuando el rendimiento o la memoria es importante?

R: Estas colecciones inmutables se han ajustado para tener características de rendimiento competitivo para las colecciones mutables mientras se equilibra el uso compartido de memoria. En algunos casos, son casi tan rápidos como las colecciones mutables tanto algorítmicamente como en tiempo real, a veces incluso más rápido, mientras que en otros casos son algorítmicamente más complejos. En muchos casos, sin embargo, la diferencia será insignificante. En general, debe usar el código más simple para realizar el trabajo y luego ajustar el rendimiento según sea necesario. Las colecciones inmutables te ayudan a escribir códigos simples, especialmente cuando se debe considerar la seguridad de las hebras.

En otras palabras, en muchos casos la diferencia no será perceptible y usted debería ir con la opción más simple, que para conjuntos concurrentes sería usar ImmutableHashSet<T> , ¡ya que no tiene una implementación mutable de bloqueo existente! :-)


En lugar de envolver un ConcurrentDictionary o bloquear un HashSet , creé un ConcurrentHashSet actual basado en ConcurrentDictionary .

Esta implementación admite operaciones básicas por artículo sin las operaciones establecidas de HashSet ya que tienen menos sentido en escenarios concurrentes OMI:

var concurrentHashSet = new ConcurrentHashSet<string>( new[] { "hamster", "HAMster", "bar", }, StringComparer.OrdinalIgnoreCase); concurrentHashSet.TryRemove("foo"); if (concurrentHashSet.Contains("BAR")) { Console.WriteLine(concurrentHashSet.Count); }

Salida: 2

Puedes obtenerlo desde NuGet here y ver la fuente en GitHub here .


La parte difícil de hacer que 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 implicados en la operación, mientras bloquea ambos conjuntos.

Perderá las ventajas de un ConcurrentDictionary<T,byte> cuando tenga que bloquear todo el conjunto durante la iteración. Sin bloqueo, estas operaciones no son seguras para hilos.

Dada la sobrecarga adicional de ConcurrentDictionary<T,byte> , probablemente sea más HashSet<T> usar el HashSet<T> más ligero y simplemente rodear todo en bloqueos.

Si no necesita las operaciones establecidas, utilice ConcurrentDictionary<T,byte> y simplemente use el default(byte) como valor cuando está agregando claves.


Prefiero las soluciones completas, así que hice esto: tenga en cuenta que mi Cuenta se implementa de una manera diferente porque no veo por qué se debería prohibir leer el hashset al intentar contar sus valores.

@ Zen, gracias por comenzar.

[DebuggerDisplay("Count = {Count}")] [Serializable] public class ConcurrentHashSet<T> : ICollection<T>, ISet<T>, ISerializable, IDeserializationCallback { private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); private readonly HashSet<T> _hashSet = new HashSet<T>(); public ConcurrentHashSet() { } public ConcurrentHashSet(IEqualityComparer<T> comparer) { _hashSet = new HashSet<T>(comparer); } public ConcurrentHashSet(IEnumerable<T> collection) { _hashSet = new HashSet<T>(collection); } public ConcurrentHashSet(IEnumerable<T> collection, IEqualityComparer<T> comparer) { _hashSet = new HashSet<T>(collection, comparer); } protected ConcurrentHashSet(SerializationInfo info, StreamingContext context) { _hashSet = new HashSet<T>(); // not sure about this one really... var iSerializable = _hashSet as ISerializable; iSerializable.GetObjectData(info, context); } #region Dispose public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposing) if (_lock != null) _lock.Dispose(); } public IEnumerator<T> GetEnumerator() { return _hashSet.GetEnumerator(); } ~ConcurrentHashSet() { Dispose(false); } public void OnDeserialization(object sender) { _hashSet.OnDeserialization(sender); } public void GetObjectData(SerializationInfo info, StreamingContext context) { _hashSet.GetObjectData(info, context); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion public void Add(T item) { _lock.EnterWriteLock(); try { _hashSet.Add(item); } finally { if(_lock.IsWriteLockHeld) _lock.ExitWriteLock(); } } public void UnionWith(IEnumerable<T> other) { _lock.EnterWriteLock(); _lock.EnterReadLock(); try { _hashSet.UnionWith(other); } finally { if (_lock.IsWriteLockHeld) _lock.ExitWriteLock(); if (_lock.IsReadLockHeld) _lock.ExitReadLock(); } } public void IntersectWith(IEnumerable<T> other) { _lock.EnterWriteLock(); _lock.EnterReadLock(); try { _hashSet.IntersectWith(other); } finally { if (_lock.IsWriteLockHeld) _lock.ExitWriteLock(); if (_lock.IsReadLockHeld) _lock.ExitReadLock(); } } public void ExceptWith(IEnumerable<T> other) { _lock.EnterWriteLock(); _lock.EnterReadLock(); try { _hashSet.ExceptWith(other); } finally { if (_lock.IsWriteLockHeld) _lock.ExitWriteLock(); if (_lock.IsReadLockHeld) _lock.ExitReadLock(); } } public void SymmetricExceptWith(IEnumerable<T> other) { _lock.EnterWriteLock(); try { _hashSet.SymmetricExceptWith(other); } finally { if (_lock.IsWriteLockHeld) _lock.ExitWriteLock(); } } public bool IsSubsetOf(IEnumerable<T> other) { _lock.EnterWriteLock(); try { return _hashSet.IsSubsetOf(other); } finally { if (_lock.IsWriteLockHeld) _lock.ExitWriteLock(); } } public bool IsSupersetOf(IEnumerable<T> other) { _lock.EnterWriteLock(); try { return _hashSet.IsSupersetOf(other); } finally { if (_lock.IsWriteLockHeld) _lock.ExitWriteLock(); } } public bool IsProperSupersetOf(IEnumerable<T> other) { _lock.EnterWriteLock(); try { return _hashSet.IsProperSupersetOf(other); } finally { if (_lock.IsWriteLockHeld) _lock.ExitWriteLock(); } } public bool IsProperSubsetOf(IEnumerable<T> other) { _lock.EnterWriteLock(); try { return _hashSet.IsProperSubsetOf(other); } finally { if (_lock.IsWriteLockHeld) _lock.ExitWriteLock(); } } public bool Overlaps(IEnumerable<T> other) { _lock.EnterWriteLock(); try { return _hashSet.Overlaps(other); } finally { if (_lock.IsWriteLockHeld) _lock.ExitWriteLock(); } } public bool SetEquals(IEnumerable<T> other) { _lock.EnterWriteLock(); try { return _hashSet.SetEquals(other); } finally { if (_lock.IsWriteLockHeld) _lock.ExitWriteLock(); } } bool ISet<T>.Add(T item) { _lock.EnterWriteLock(); try { return _hashSet.Add(item); } finally { if (_lock.IsWriteLockHeld) _lock.ExitWriteLock(); } } public void Clear() { _lock.EnterWriteLock(); try { _hashSet.Clear(); } finally { if (_lock.IsWriteLockHeld) _lock.ExitWriteLock(); } } public bool Contains(T item) { _lock.EnterWriteLock(); try { return _hashSet.Contains(item); } finally { if (_lock.IsWriteLockHeld) _lock.ExitWriteLock(); } } public void CopyTo(T[] array, int arrayIndex) { _lock.EnterWriteLock(); try { _hashSet.CopyTo(array, arrayIndex); } finally { if (_lock.IsWriteLockHeld) _lock.ExitWriteLock(); } } public bool Remove(T item) { _lock.EnterWriteLock(); try { return _hashSet.Remove(item); } finally { if (_lock.IsWriteLockHeld) _lock.ExitWriteLock(); } } public int Count { get { _lock.EnterWriteLock(); try { return _hashSet.Count; } finally { if(_lock.IsWriteLockHeld) _lock.ExitWriteLock(); } } } public bool IsReadOnly { get { return false; } } }


Tu implementación es correcta El Framework .NET no proporciona un tipo de hashset simultáneo incorporado, desafortunadamente. Sin embargo, hay algunas soluciones.

ConcurrentDictionary (recomendado)

El primero es usar la clase ConcurrentDictionary<TKey, TValue> en el espacio de nombres System.Collections.Concurrent . En el caso, el valor no tiene sentido, entonces podemos usar un byte simple (1 byte en la memoria).

private ConcurrentDictionary<string, byte> _data;

Esta es la opción recomendada porque el tipo es seguro para subprocesos y proporciona las mismas ventajas que un HashSet<T> excepto que la clave y el valor son objetos diferentes.

Fuente: Social MSDN

ConcurrentBag

Si no te molestan las entradas duplicadas, puedes usar la clase ConcurrentBag<T> en el mismo espacio de nombres de la clase anterior.

private ConcurrentBag<string> _data;

Auto implementación

Finalmente, como lo hizo, puede implementar su propio tipo de datos, usando el bloqueo u otras formas que el .NET le proporciona para que sea seguro 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 concurrente oficialmente, incluso para operaciones de lectura.

Cito el código de la publicación vinculada (originalmente escrito por Ben Mosher ).

using System.Collections.Generic; using System.Threading; namespace BlahBlah.Utilities { public class ConcurrentHashSet<T> : IDisposable { private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); private readonly HashSet<T> _hashSet = new HashSet<T>(); #region Implementation of ICollection<T> ...ish public bool Add(T item) { _lock.EnterWriteLock(); try { return _hashSet.Add(item); } finally { if (_lock.IsWriteLockHeld) _lock.ExitWriteLock(); } } public void Clear() { _lock.EnterWriteLock(); try { _hashSet.Clear(); } finally { if (_lock.IsWriteLockHeld) _lock.ExitWriteLock(); } } public bool Contains(T item) { _lock.EnterReadLock(); try { return _hashSet.Contains(item); } finally { if (_lock.IsReadLockHeld) _lock.ExitReadLock(); } } public bool Remove(T item) { _lock.EnterWriteLock(); try { return _hashSet.Remove(item); } finally { if (_lock.IsWriteLockHeld) _lock.ExitWriteLock(); } } public int Count { get { _lock.EnterReadLock(); try { return _hashSet.Count; } finally { if (_lock.IsReadLockHeld) _lock.ExitReadLock(); } } } #endregion #region Dispose public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposing) if (_lock != null) _lock.Dispose(); } ~ConcurrentHashSet() { Dispose(false); } #endregion } }

EDITAR: Mueva los métodos de bloqueo de entrada fuera de los bloques try , ya que podrían lanzar una excepción y ejecutar las instrucciones contenidas en los bloques finally .