ReaderWriterLockSlim C#的缓存数据字典

时间:2015-03-21 11:51:50

标签: c# multithreading caching dictionary

我想就可以改进或更改当前代码设计的地方提出一些建议。

有一个设置的Manager类,并且有一个由不同线程调用的Calculation方法。每个线程都有一个需要计算的资源。这些资源可能属于不同的公司。 对于每个公司,我们想要缓存一些数据,但是我们只能在调用Calculate方法时通过资源获取公司数据。

所以我目前的想法是在Manager类中使用DictionaryResourceTag作为Key的Dictionary。 调用Calculate时,确定companyResourceTag并调用方法CheckCachedData。

private void CheckCachedData(int companyResourceTag)
    {
        _ReaderWriterLock.EnterUpgradeableReadLock();
        try
        {
            if (!_CompanyCachedData.ContainsKey(companyResourceTag))
            {                 
                    Elements elements = ElementService.GetAllElements();
                    DataElements dataElements = ElementService.GetAllDataElements();
                    CalendarGroupings calendarGroupings = CalendarService.GetAllCalendarGroupings();                       

                    CachedDataContainer cachedItem = new CachedDataContainer(elements, dataElements, calendarGroupings);

                    _ReaderWriterLock.EnterWriteLock();
                    try
                    {
                         _CompanyCachedData.Add(companyResourceTag, cachedItem);
                    }
                    finally
                    {
                        _ReaderWriterLock.ExitWriteLock();
                    }
            }
        }
        finally
        {
            _ReaderWriterLock.ExitUpgradeableReadLock();
        }
    }

如果此公司没有以前的资源,则必须通过服务获取该公司的数据。基础表不会经常更改,我们可以假设在为所有资源运行计算期间表将保持不变。但是,获取此数据非常耗时。因此需要缓存。

可能会说100家不同的公司,以及需要计算的30000多种资源。还有一些其他地方(每个资源)从中读取缓存数据,例如:

        _ReaderWriterLock.EnterReadLock();
        try
        {
            _CompanyCachedData.TryGetValue(companyResourceTag, out cachedDataContainer);
        }
        finally
        {
            _ReaderWriterLock.ExitReadLock();
        }

       //Do something with cachedDataContainer

由于Eric Lathrop的评论,我没有尝试使代码更优雅: Possible problem

我没有使用ConcurrentDictionary,因为这里提到的问题是:Possible problem with ConcurrentDictionary

我不确定普通锁是否比ReaderWriterLockSlim更好,但我喜欢这样的想法,即一次可能有多个读取器,并且我可以升级锁。目前我也更关心正确而不是速度。

我是否正确使用了RWLS? 您是否同意我目前使用的UpgradeableReadLock? 你是否同意我选择不使用ConcurrentDictionary?

1 个答案:

答案 0 :(得分:2)

如果没有a good, minimal, complete code example,就无法对代码做出任何肯定的说法。但是,您发布的代码在基本正确性方面似乎没有问题。

但是...

  

您是否同意我目前使用的是UpgradeableReadLock?

我通常希望将检索和缓存存储视为同一操作的一部分。即有一个主要负责检索值的方法,如果没有找到,则会以慢速方式获取值,将其存储在缓存中,然后返回。

将缓存检索和原始提取拆分为两个单独的方法似乎很奇怪且可能有错误(但是没有一个好的代码示例,我无法肯定地说)。

(注意:通过上面的描述,我只是指调用者看到的API。当然,实际的缓存实现会将实际数据提取到一个单独的方法中是有意义的。它是' s只是我希望看到用于从缓存中检索值的相同方法调用的方法,这样你只需要在值实际上已经在缓存中的情况下进行一次读锁定。) EM>

  

您是否同意我选择不使用ConcurrentDictionary?

同样,缺乏更多背景,很难说。我倾向于避免ConcurrentDictionary,因为与传统的Dictionary<TKey, TValue>类相比,它有一些难以使用的语义,至少在利用其特定于并发性的特性时。

但在您的情况下,我认为那些特定于并发性的功能可能是您正在寻找的功能。特别是,TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory)方法实现了缓存通常需要的语义:检索现有值,或者如果密钥不存在,则使用新值填充字典。

另一方面,使用该方法有一个重要的警告:如果从多个线程同时调用该方法,则可以多次调用valueFactory委托。实际上只有一个结果用于填充字典,它可能会或可能不会对本地性能产生影响,但它显然会影响您从中获取数据的服务器上的总体负载。

如果这是一个问题,那么ReaderWriterLockSlim可能是一个更好的选择,因为它可以让你确保只有一个作家。

第三方面,我会在此处注明我之前关于相对表现的评论。也就是说,ReaderWriterLockSlim的潜在性能优势在此非常重要。从理论上讲,与使用Monitor类(即lock语句)相比,已经填充的案例应该有更少的开销,但我认为你必须处理一个实际发挥作用的争论非常激烈。

从您的示例中甚至不清楚companyResourceTag如何映射到缓存键的给定数据组,更不用说您期望看到代码从缓存中检索已获取的数据的频率(给定获取数据的假设成本,我假设高速缓存未命中的同步成本并不受关注。因此,在准确答案所需的问题中缺少许多重要细节。

我猜如果非常高级别的争用,而且非常罕见,ReaderWriterLockSlim 可能为您提供可衡量的绩效改进。但除此之外,我希望lock能够以完全可接受的性能提供更简单的代码。

当然,这完全是纯粹的猜测。同样,如果没有完整的代码示例,确实无法确定哪种同步机制在您的方案中最佳。但考虑到获取数据的假设成本,似乎管理自己的同步可能比使用ConcurrentDictionary<TKey, TValue>更好,因为后者并没有提供避免获取数据的直接方法。一次。

您是否应该使用ReaderWriterLockSlim而不是更简单的lock方法,更具推测性。它可能可能前者更好,但缺少更多信息,它可能不是。