2012-02-16 17 views
5

Ho un problema interessante con deadlock in una mia applicazione. Esiste un archivio dati in memoria che utilizza ReaderWriterLockSlim per sincronizzare letture e scritture. Uno dei metodi di lettura utilizza Parallel.ForEach per cercare nel negozio una serie di filtri. È possibile che uno dei filtri richieda una lettura costante dello stesso negozio. Ecco lo scenario che sta producendo un deadlock:Deadlock in Parallel.ForEach con ReaderWriterLockSlim

UPDATE: Esempio di codice qui sotto. Passi aggiornati con metodo effettivo chiamate
determinata istanza Singleton store di ConcreteStoreThatExtendsGenericStore

  1. Thread1 ottiene un blocco di lettura per l'archivio - store.Search(someCriteria)
  2. Thread2 tentativi di aggiornare l'archivio con un blocco di scrittura - store.Update() - , blocchi dietro Thread1
  3. Thread1 esegue Parallel.F foreach contro il negozio per eseguire una serie di filtri
  4. Thread3 (generati da Filettatura1 s' Parallel.ForEach) tenta un tempo costante lettura del negozio. Prova a ottenere un blocco di lettura ma è bloccato dietro il blocco scrittura Thread2.
  5. Thread1 Impossibile completare perché non è possibile unire Thread3. Thread2 non può terminare perché è bloccato dietro Thread1.

Idealmente quello che mi piacerebbe fare è non cercare di acquisire un blocco di lettura, se un thread antenato del thread corrente ha già lo stesso blocco. C'è un modo per fare questo? O c'è un altro/approccio migliore?

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

L'archivio in memoria è un tipo personalizzato o è un elenco che è un campo all'interno di un'altra classe? –

+0

Passa ciò che hai bloccato nel metodo che stai utilizzando in foreach, altrimenti parallelarlo da leggere in avanti è una perdita di tempo. Non funzioneranno mai in parallelo perché sono tutti con bottleneck sulla stessa risorsa. –

+0

@TrevorPilley: Il negozio è un dizionario , con Parallel.ForOgni sopra i valori – eakins05

risposta

1

Non è chiaro che tipo di concorrenza, memoria e requisiti di prestazioni in realtà hanno ecco sono alcune opzioni.

Se si utilizza .Net 4.0, è possibile sostituire il Dictionary con un ConcurrentDictionary e rimuovere lo ReaderWriterLockSlim. Tieni presente che facendo ciò ridurrai il tuo ambito di blocco e cambierai la semantica del metodo, consentendo modifiche al contenuto mentre stai enumerando (tra le altre cose), ma d'altra parte questo ti darà un enumeratore thread-safe che non bloccherà legge o scrive. Dovrai determinare se questo è un cambiamento accettabile per la tua situazione.

Se è davvero necessario bloccare l'intera raccolta in questo modo, è possibile supportare un criterio di blocco ricorsivo (new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion)) se è possibile mantenere tutte le operazioni sullo stesso thread. Sta eseguendo la tua ricerca in parallelo una necessità?

In alternativa, è possibile ottenere semplicemente un'istantanea della raccolta corrente di valori (bloccando l'operazione) e quindi eseguire la ricerca sullo snapshot. Non sarà garantito avere gli ultimi dati e dovrai dedicare un po 'di tempo alla conversione, ma forse è un compromesso accettabile per la tua situazione.

+0

Mi piace l'approccio ConcurrentDictionary, ma sfortunatamente manteniamo più della sola memoria (indici, ecc.), Che devono essere dietro il blocco di scrittura. Rimuovere la parallelizzazione potrebbe essere un'opzione che dovremo eliminare. L'idea dell'istantanea è qualcosa con cui ci siamo divertiti prima, ma con un cambio di paradigma del genere, stiamo pensando di acquistare una soluzione di caching per rimpiazzare quella legacy homegrown, quindi probabilmente faremmo comunque quella strada. Grazie per l'intuizione! – eakins05