5

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

更新:下面的示例代码。使用实际方法调用更新的步骤给定的
单例实例storeConcreteStoreThatExtendsGenericStore

  1. Thread1在存储上获得读锁 - store.Search(someCriteria)
  2. Thread2尝试使用写锁更新存储 - store.Update()- 阻塞Thread1
  3. Thread1对存储执行 Parallel.ForEach 以运行一组过滤器
  4. Thread3(由Thread1的 Parallel.ForEach 产生)尝试对存储进行恒定时间读取。它试图获得读锁,但被Thread2的写锁阻塞。
  5. Thread1无法完成,因为它无法加入Thread3Thread2无法完成,因为它被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 回答