【问题标题】:Dictionary of cached data with ReaderWriterLockSlim C#使用 ReaderWriterLockSlim C# 缓存数据字典
【发布时间】:2015-03-21 11:51:49
【问题描述】:

我想要一些关于在哪里可以改进或更改当前代码的设计的建议。

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

所以我目前的想法是在Manager类中有一个Dictionary,以companyResourceTag为Key。 调用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 的选择吗?

【问题讨论】:

  • “如果我想提高速度”——意思是什么?如果没有明确说明观察到的性能问题,以及明确说明的性能目标,那么这个问题就太宽泛了。您有什么证据表明您现在拥有的代码还不够快?鉴于您总是有可能必须通过网络获取数据,并且这种延迟是可以接受的,很难相信即使是一个简单的锁也会导致任何真正的问题。如果 RWLS 有效,您在这里有什么具体问题?
  • @Peter Duniho:我已经编辑了原始帖子以便更具体。

标签: c# multithreading caching dictionary


【解决方案1】:

没有a good, minimal, complete code example 无法确定有关代码的任何内容。但是,就基本正确性而言,您发布的代码似乎还不错。

但是……

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

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

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

(注意:以上,我只是指调用者看到的 API。当然,实际的缓存实现会将实际数据获取分解为单独的方法是有道理的。只是我会希望看到该方法被用于从缓存中检索值的相同方法调用,因此如果值实际上已经在缓存中,您只需获取一次读取锁。 p>

您是否同意我选择不使用 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 方法,更具推测性。 可能前者更好,但如果没有更多信息,它也很可能不是。

【讨论】:

  • 第三只手肯定是另一只手,还是第一只手? :-)
  • @Peter Duniho,谢谢你的回答到目前为止很有用。我将很快编辑代码以进一步澄清它。但是,据我了解,只能有一个线程持有 UpgradeableReadLock (尽管它允许其他线程在持有 Readlock 的情况下完成),因此我认为您在第三手下提出的观点是不可能的。 MSDN link
  • @Mikrur:对不起,是的,你是对的。我应该在写之前仔细检查我的记忆。我已编辑问题以删除该错误评论,并根据(诚然含糊的)问题陈述澄清我的总体(并且公认是含糊的)想法和结论。
  • @Peter Duniho:我为这个模糊的问题道歉。正如链接所解释的那样,我目前无法在任何合理的时间内提供一个最小的、完整的和可验证的程序。 CompanyResourceTag 用于检索我未包含在代码块中的数据的方法(例如元素、DataElements 和 CalendarGroupings 的数据)。我可以理解您对此感到沮丧,但非常感谢您花时间给出推测性的答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-09-29
  • 2013-08-27
  • 2012-06-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-13
相关资源
最近更新 更多