Pude implementar un diccionario seguro para subprocesos en C # derivando de IDictionary y definiendo un objeto SyncRoot privado:
public class SafeDictionary<TKey, TValue>: IDictionary<TKey, TValue>
{
private readonly object syncRoot = new object();
private Dictionary<TKey, TValue> d = new Dictionary<TKey, TValue>();
public object SyncRoot
{
get { return syncRoot; }
}
public void Add(TKey key, TValue value)
{
lock (syncRoot)
{
d.Add(key, value);
}
}
// more IDictionary members...
}
Luego bloqueo este objeto SyncRoot en todos mis consumidores (múltiples subprocesos):
Ejemplo:
lock (m_MySharedDictionary.SyncRoot)
{
m_MySharedDictionary.Add(...);
}
Pude hacerlo funcionar, pero esto resultó en un código desagradable. Mi pregunta es, ¿existe una forma mejor y más elegante de implementar un diccionario seguro para subprocesos?
Respuestas:
Como dijo Peter, puede encapsular toda la seguridad de los subprocesos dentro de la clase. Deberá tener cuidado con cualquier evento que exponga o agregue, asegurándose de que se invoquen fuera de los bloqueos.
Editar: Los documentos de MSDN señalan que la enumeración no es inherentemente segura para subprocesos. Esa puede ser una de las razones para exponer un objeto de sincronización fuera de su clase. Otra forma de abordar eso sería proporcionar algunos métodos para realizar una acción en todos los miembros y bloquear la enumeración de los miembros. El problema con esto es que no sabe si la acción pasada a esa función llama a algún miembro de su diccionario (eso resultaría en un punto muerto). Exponer el objeto de sincronización permite al consumidor tomar esas decisiones y no oculta el punto muerto dentro de su clase.
fuente
La clase .NET 4.0 que admite la simultaneidad se denomina
ConcurrentDictionary
.fuente
Es casi seguro que intentar sincronizar internamente será insuficiente porque tiene un nivel de abstracción demasiado bajo. Supongamos que hace que las operaciones
Add
yContainsKey
sean seguras para subprocesos individualmente de la siguiente manera:Entonces, ¿qué sucede cuando llamas a este bit de código supuestamente seguro para subprocesos desde varios subprocesos? ¿Siempre funcionará bien?
La respuesta simple es no. En algún momento, el
Add
método lanzará una excepción que indica que la clave ya existe en el diccionario. ¿Cómo puede ser esto con un diccionario seguro para subprocesos? Bueno, solo porque cada operación es segura para subprocesos, la combinación de dos operaciones no lo es, ya que otro subproceso podría modificarlo entre su llamada aContainsKey
yAdd
.Lo que significa que para escribir este tipo de escenario correctamente, necesita un candado fuera del diccionario, por ejemplo
Pero ahora, dado que tiene que escribir código de bloqueo externo, está mezclando la sincronización interna y externa, lo que siempre conduce a problemas como código poco claro y puntos muertos. Entonces, en última instancia, probablemente sea mejor para:
Use un normal
Dictionary<TKey, TValue>
y sincronice externamente, encerrando las operaciones compuestas en él, oEscriba un nuevo contenedor seguro para subprocesos con una interfaz diferente (es decir, no
IDictionary<T>
) que combine las operaciones, como unAddIfNotContained
método, para que nunca tenga que combinar operaciones desde él.(Tiendo a ir con el # 1 yo mismo)
fuente
No debe publicar su objeto de bloqueo privado a través de una propiedad. El objeto de la cerradura debe existir de forma privada con el único propósito de actuar como punto de encuentro.
Si el rendimiento resulta ser deficiente con el candado estándar, la colección de candados Power Threading de Wintellect puede ser muy útil.
fuente
Hay varios problemas con el método de implementación que está describiendo.
Personalmente, he descubierto que la mejor manera de implementar una clase segura para subprocesos es a través de la inmutabilidad. Realmente reduce la cantidad de problemas que puede encontrar con la seguridad de subprocesos. Consulte el blog de Eric Lippert para obtener más detalles.
fuente
No es necesario bloquear la propiedad SyncRoot en sus objetos de consumidor. El bloqueo que tienes dentro de los métodos del diccionario es suficiente.
Para elaborar: Lo que termina sucediendo es que su diccionario está bloqueado por un período de tiempo más largo del necesario.
Lo que ocurre en tu caso es lo siguiente:
Digamos que el hilo A adquiere el bloqueo en SyncRoot antes de la llamada a m_mySharedDictionary.Add. Luego, el hilo B intenta adquirir el bloqueo, pero se bloquea. De hecho, todos los demás subprocesos están bloqueados. El subproceso A puede llamar al método Add. En la declaración de bloqueo dentro del método Add, el subproceso A puede obtener el bloqueo nuevamente porque ya lo posee. Al salir del contexto de bloqueo dentro del método y luego fuera del método, el hilo A ha liberado todos los bloqueos permitiendo que otros hilos continúen.
Simplemente puede permitir que cualquier consumidor llame al método Add, ya que la instrucción de bloqueo dentro de su método Add de la clase SharedDictionary tendrá el mismo efecto. En este momento, tiene un bloqueo redundante. Solo bloquearía SyncRoot fuera de uno de los métodos de diccionario si tuviera que realizar dos operaciones en el objeto de diccionario que debían garantizarse que ocurran consecutivamente.
fuente
Solo un pensamiento, ¿por qué no recrear el diccionario? Si la lectura es una multitud de escritura, el bloqueo sincronizará todas las solicitudes.
ejemplo
fuente
Colecciones y sincronización
fuente