5

アプリケーションのデッドロックに関する興味深い問題があります。ReaderWriterLockSlim を使用して読み取りと書き込みを同期するインメモリ データ ストアがあります。読み取りメソッドの 1 つは、Parallel.ForEach を使用して、一連のフィルターを指定してストアを検索します。フィルターの 1 つが同じストアの一定時間の読み取りを必要とする可能性があります。デッドロックが発生するシナリオは次のとおりです。

更新:以下のコード例。
実際のメソッド呼び出しで更新されたstoreステップConcreteStoreThatExtendsGenericStore

  1. スレッド1 はストアで読み取りロックを取得します - store.Search(someCriteria)
  2. スレッド 2は、書き込みロックでストアを更新しようとします - -、スレッド 1store.Update()の後ろのブロック
  3. Thread1はストアに対して Parallel.ForEach を実行し、一連のフィルターを実行します。
  4. Thread3 ( Thread1の Parallel.ForEach によって生成される) は、ストアの一定時間の読み取りを試みます。読み取りロックを取得しようとしますが、Thread2の書き込みロックの背後でブロックされます。
  5. Thread1はThread3に参加できないため、終了できません。 Thread2はThread1の背後でブロックされているため、終了できません。

理想的には、現在のスレッドの祖先スレッドが既に同じロックを持っている場合、読み取りロックを取得しようとしないことです。これを行う方法はありますか?または、別の/より良いアプローチがありますか?

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

1 に答える 1

2

並行性、メモリ、およびパフォーマンスの要件が実際にどのようなものかは不明であるため、いくつかのオプションを以下に示します。

.Net 4.0 を使用している場合は、Dictionaryを aConcurrentDictionaryに置き換えて、ReaderWriterLockSlim. これを行うと、ロック範囲が縮小され、メソッドのセマンティクスが変更され、(とりわけ) 列挙中にコンテンツを変更できるようになりますが、一方で、ブロックされないスレッドセーフな列挙子が得られることに注意してください。読み取りまたは書き込み。それがあなたの状況にとって許容可能な変更であるかどうかを判断する必要があります.

この方法でコレクション全体をロックダウンする必要が本当にある場合はnew ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion)、すべての操作を同じスレッドに保持できる場合は、再帰的なロック ポリシー ( ) をサポートできる可能性があります。検索を並行して実行する必要がありますか?

または、現在の値のコレクションのスナップショットを取得して (その操作をロックして)、スナップショットに対して検索を実行することもできます。最新のデータが保証されるわけではなく、変換に少し時間を費やす必要がありますが、状況によっては許容できるトレードオフかもしれません。

于 2012-02-17T18:14:41.400 に答える