简体   繁体   English

与ReaderWriterLockSlim并行的Parallel.ForEach死锁

[英]Deadlock in Parallel.ForEach with ReaderWriterLockSlim

I have an interesting problem with deadlocks in an my application. 我的应用程序中有一个有趣的死锁问题。 There is an in-memory data store that uses a ReaderWriterLockSlim to synchronize reads and writes. 有一个内存数据存储,它使用ReaderWriterLockSlim来同步读写。 One of the read methods uses Parallel.ForEach to search the store given a set of filters. 其中一种读取方法使用Parallel.ForEach在给定一组过滤器的情况下搜索商店。 It's possible that one of the filters requires a constant-time read of same store. 其中一个过滤器可能需要对同一商店进行恒定时间读取。 Here is the scenario that's producing aa deadlock: 这是产生死锁的场景:

UPDATE: Example code below. 更新:下面的示例代码。 Steps updated with actual method calls 使用实际方法调用更新步骤
Given singleton instance store of ConcreteStoreThatExtendsGenericStore 给定ConcreteStoreThatExtendsGenericStore单例实例store

  1. Thread1 gets a read lock on the store - store.Search(someCriteria) Thread1获取商店的读锁定 - store.Search(someCriteria)
  2. Thread2 attempts to update the store with a write lock - store.Update() -, blocks behind Thread1 线程2尝试与写锁定来更新存储- store.Update() - ,后面线程1
  3. Thread1 executes Parallel.ForEach against the store to run a set of filters Thread1对商店执行Parallel.ForEach以运行一组过滤器
  4. Thread3 (spawned by Thread1 's Parallel.ForEach) attempts a constant-time read of the store. Thread3(线程1的Parallel.ForEach催生)尝试恒定时读取的商店。 It tries to get a read lock but is blocked behind Thread2 's write lock. 它尝试获取读锁定但在Thread2的写锁定后被阻止。
  5. Thread1 cannot finish because it can't join Thread3 . Thread1无法完成,因为它无法加入Thread3 Thread2 can't finish because it's blocked behind Thread1 . 线程2无法完成,因为它阻止后面线程1。

Ideally what I'd like to do is not try to acquire a read lock if an ancestor thread of the current thread already has the same lock. 理想情况下,如果当前线程的祖先线程已经具有相同的锁,我想要做的就是尝试获取读锁。 Is there any way to do this? 有没有办法做到这一点? Or is there a another/better approach? 或者还有另一种/更好的方法吗?

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

It's unclear what kind of concurrency, memory and performance requirements you actually have so here are a few options. 目前还不清楚你实际拥有什么样的并发性,内存和性能要求,所以这里有几个选项。

If you are using .Net 4.0, you could replace your Dictionary with a ConcurrentDictionary and remove your ReaderWriterLockSlim . 如果您使用的是.Net 4.0,则可以使用ConcurrentDictionary替换Dictionary并删除ReaderWriterLockSlim Keep in mind that doing that will reduce your locking scope and change your method semantics, allowing changes to the contents while you're enumerating (among other things), but on the other hand that will give you a threadsafe enumerator that won't block reads or writes. 请记住,这样做会减少锁定范围并更改方法语义,允许在枚举(包括其他内容)时更改内容,但另一方面,这将为您提供不会阻塞的线程安全枚举器读或写。 You'll have to determine if that's an acceptable change for your situation. 您必须确定这是否适合您的情况。

If you really do need to lock down the entire collection in this way, you might be able to support a recursive lock policy ( new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion) ) if you can keep all operations on the same thread. 如果您确实需要以这种方式锁定整个集合,那么您可以支持递归锁定策略( new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion) ),如果您可以在同一个线程上保留所有操作。 Is performing your search in parallel a necessity? 并行执行搜索是必要的吗?

Alternately, you may want to just get a snapshot of your current collection of values (locking around that operation) and then perform your search against the snapshot. 或者,您可能只想获取当前值集的快照(锁定该操作),然后针对快照执行搜索。 It won't be guaranteed to have the latest data and you'll have to spend a little time on conversion, but maybe that's an acceptable tradeoff for your situation. 我们无法保证获得最新数据,您将不得不花一点时间进行转换,但也许这对您的情况来说是可以接受的权衡。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM