与ReaderWriterLockSlim并行的Parallel.ForEach死锁

时间:2012-02-16 23:02:14

标签: c# deadlock readerwriterlockslim parallel.foreach

我的应用程序中有一个有趣的死锁问题。有一个内存数据存储,它使用ReaderWriterLockSlim来同步读写。其中一种读取方法使用Parallel.ForEach在给定一组过滤器的情况下搜索商店。其中一个过滤器可能需要对同一商店进行恒定时间读取。这是导致死锁的场景:

更新:以下示例代码。用实际方法调用更新的步骤
给定store

的单例实例ConcreteStoreThatExtendsGenericStore
  1. Thread1 获取商店的读锁定权限 - store.Search(someCriteria)
  2. Thread2 尝试使用写锁定更新商店 - store.Update() - ,阻止 Thread1
  3. Thread1 对商店执行Parallel.ForEach以运行一组过滤器
  4. Thread3 (由 Thread1 的Parallel.ForEach生成)尝试对商店进行固定时间读取。它尝试获取读锁定但在 Thread2 的写锁定后被阻止。
  5. Thread1 无法完成,因为无法加入 Thread3 Thread2 无法完成,因为它在 Thread1 后面被阻止。
  6. 理想情况下,我想要做的是 not 如果当前线程的祖先线程已经具有相同的锁,则尝试获取读锁。有没有办法做到这一点?或者还有另一种/更好的方法吗?

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

1 个答案:

答案 0 :(得分:1)

目前还不清楚您实际拥有哪种并发,内存和性能要求,因此这里有几个选项。

如果您使用的是.Net 4.0,则可以将Dictionary替换为ConcurrentDictionary并删除ReaderWriterLockSlim。请记住,这样做会减少锁定范围并更改方法语义,允许在枚举(包括其他内容)时更改内容,但另一方面,这将为您提供不会阻塞的线程安全枚举器读或写。您必须确定这是否适合您的情况。

如果您确实需要以这种方式锁定整个集合,则可以支持递归锁定策略(new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion)),如果您可以将所有操作保留在同一个线程上。并行执行搜索是必要的吗?

或者,您可能只想获取当前值集的快照(锁定该操作),然后针对快照执行搜索。我们无法保证获得最新数据,您将不得不花一点时间进行转换,但也许这对您的情况来说是可以接受的权衡。