2012-02-16 15 views
5

Tengo un problema interesante con interbloqueos en mi aplicación. Hay un almacén de datos en memoria que usa un ReaderWriterLockSlim para sincronizar lecturas y escrituras. Uno de los métodos de lectura usa Parallel.ForEach para buscar en la tienda dado un conjunto de filtros. Es posible que uno de los filtros requiera una lectura constante de la misma tienda. Aquí está el escenario que está produciendo un punto muerto:Interbloqueo en Paralelo.ParaEach con ReaderWriterLockSlim

ACTUALIZACIÓN: Ejemplo de código a continuación. Pasos actualizados con el método real de las llamadas
Dada instancia singleton store de ConcreteStoreThatExtendsGenericStore

  1. Thread1 consigue un bloqueo de lectura en la tienda - store.Search(someCriteria)
  2. Thread2 intentos para actualizar la tienda con un bloqueo de escritura - store.Update() - , bloques detrás de Thread1
  3. Thread1 ejecuta Parallel.F orEach en el almacén para ejecutar un conjunto de filtros
  4. Thread3 (generados por Thread1 's Parallel.ForEach) intenta una lectura del almacén constante en el tiempo. Intenta obtener un bloqueo de lectura pero está bloqueado detrás del bloqueo de escritura de Thread2.
  5. Thread1 no puede terminar porque no puede unirse a Thread3. Thread2 no puede terminar porque está bloqueado detrás de Thread1.

Lo ideal sería que lo que me gustaría hacer es no tratar de adquirir un bloqueo de lectura si un hilo ancestro del hilo actual ya tiene la misma cerradura. ¿Hay alguna manera de hacer esto? ¿O hay otro enfoque/mejor?

public abstract class GenericStore<TKey, TValue> 
{ 
    private ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); 
    private List<IFilter> _filters; //contains instance of ExampleOffendingFilter 

    protected Dictionary<TKey, TValue> Store { get; private set; } 

    public void Update() 
    { 
     _lock.EnterWriterLock(); 
     //update the store 
     _lock.ExitWriteLock(); 
    } 

    public TValue GetByKey(TKey key) 
    { 
     TValue value; 
     //TODO don't enter read lock if current thread 
     //was started by a thread holding this lock 
     _lock.EnterReadLock(); 
     value = Store[key]; 
     _lock.ExitReadLock(); 
     return value; 
    } 

    public List<TValue> Search(Criteria criteria) 
    { 
     List<TValue> matches = new List<TValue>(); 
     //TODO don't enter read lock if current thread 
     //was started by a thread holding this lock 
     _lock.EnterReadLock(); 
     Parallel.ForEach(Store.Values, item => 
     { 
      bool isMatch = true; 
      foreach(IFilter filter in _filters) 
      { 
       if (!filter.Check(criteria, item)) 
       { 
        isMatch = false; 
        break; 
       } 
      } 
      if (isMatch) 
      { 
       lock(matches) 
       { 
        matches.Add(item); 
       } 
      } 
     }); 
     _lock.ExitReadLock(); 
     return matches; 
    } 
} 

public class ExampleOffendingFilter : IFilter 
{ 
    private ConcreteStoreThatExtendsGenericStore _sameStore; 

    public bool Check(Criteria criteria, ConcreteValueType item) 
    { 
     _sameStore.GetByKey(item.SomeRelatedProperty); 
     return trueOrFalse; 
    } 
} 
+0

¿La tienda en la memoria es un tipo personalizado o es una lista que es un campo dentro de otra clase? –

+0

Pase lo que haya bloqueado en el método que está ejecutando en foreach; de lo contrario, la paralelización de la lectura en adelante es una pérdida de tiempo. Nunca se ejecutará en paralelo porque todos están embotellados en el mismo recurso. –

+0

@TrevorPilley: La tienda es un diccionario , con Parallel.ForEach sobre los valores – eakins05

Respuesta

1

No está claro qué tipo de concurrencia, la memoria y los requisitos de rendimiento que realmente tiene lo que aquí hay algunas opciones.

Si está utilizando .Net 4.0, puede reemplazar su Dictionary con un ConcurrentDictionary y eliminar su ReaderWriterLockSlim. Tenga en cuenta que al hacerlo se reducirá su ámbito de bloqueo y se cambiará la semántica de su método, permitiendo cambios en los contenidos mientras se enumera (entre otras cosas), pero por otro lado, obtendrá un enumerador de hilos que no bloqueará lee o escribe Deberá determinar si ese es un cambio aceptable para su situación.

Si realmente necesita bloquear toda la colección de esta manera, es posible que pueda admitir una política de bloqueo recursivo (new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion)) si puede mantener todas las operaciones en el mismo hilo. ¿Realizar su búsqueda en paralelo es una necesidad?

O bien, puede obtener una instantánea de su colección actual de valores (bloqueando esa operación) y luego realizar su búsqueda en la instantánea. No se garantizará que tenga los últimos datos y tendrá que invertir un poco de tiempo en la conversión, pero tal vez sea una solución aceptable para su situación.

+0

Me gusta el enfoque ConcurrentDictionary, pero desafortunadamente no solo guardamos el almacenamiento (índices, etc.) que tiene que estar detrás del bloqueo de escritura. La eliminación de la paralelización puede ser una opción que tendremos que eliminar POC. La idea de la instantánea es algo con lo que hemos jugado antes, pero con un cambio de paradigma como ese, estamos contemplando la compra de una solución de almacenamiento en caché para reemplazar esta de origen local, por lo que probablemente seguiremos esa ruta de todos modos. Gracias por la visión! – eakins05

Cuestiones relacionadas