【问题标题】:Is locking necessary in this ConcurrentDictionary caching scenario在此 ConcurrentDictionary 缓存方案中是否需要锁定
【发布时间】:2012-08-28 15:58:00
【问题描述】:

我有以下代码来缓存我在多线程应用程序中使用的并发字典中某些类的实例。

简单地说,当我使用 id 参数实例化类时,它首先检查字典中是否存在具有给定 id 的 privateclass 实例,如果不存在则创建 privateclass 的实例(这需要很长时间,有时需要几个秒),并将其添加到字典中以供将来使用。

public class SomeClass
{
    private static readonly ConcurrentDictionary<int, PrivateClass> SomeClasses =
        new ConcurrentDictionary<int, PrivateClass>();

    private readonly PrivateClass _privateClass;

    public SomeClass(int cachedInstanceId)
    {
        if (!SomeClasses.TryGetValue(cachedInstanceId, out _privateClass))
        {
            _privateClass = new PrivateClass(); // This takes long time
            SomeClasses.TryAdd(cachedInstanceId, _privateClass);
        }
    }

    public int SomeCalculationResult()
    {
        return _privateClass.CalculateSomething();
    }

    private class PrivateClass
    {
        internal PrivateClass()
        {
            // this takes long time
        }

        internal int CalculateSomething()
        {
            // Calculates and returns something
        }
    }
}

我的问题是,我是否需要在外部类构造函数的生成和赋值部分周围添加一个锁,以使这段代码线程安全,还是它本来就很好?

更新:

在 SLaks 的suggestion 之后,尝试使用 ConcurrentDictionary 的GetOrAdd() 方法与Lazy 的组合,但不幸的是PrivateClass 的构造函数仍然调用了不止一次。测试代码见https://gist.github.com/3500955

更新 2: 您可以在此处查看最终解决方案: https://gist.github.com/3501446

【问题讨论】:

标签: c# multithreading concurrency


【解决方案1】:

你在滥用ConcurrentDictionary

在多线程代码中,您永远不应该检查项目是否存在,如果不存在则添加它。
如果两个线程同时运行该代码,它们最终都会添加它。

一般来说,这类问题有两种解决方案。您可以将所有代码封装在一个锁中,或者您可以在一个原子操作中将其重新设计为整个代码。

ConcurrentDictionary 就是为这种场景而设计的。

你应该简单地调用

 _privateClass = SomeClasses.GetOrAdd(cachedInstanceId, key => new PrivateClass());

【讨论】:

【解决方案2】:

锁定不是必需的,但您所做的不是线程安全的。您应该使用ConcurrentDictionary.GetOrAdd() 在一个原子操作中完成所有操作,而不是先检查字典中是否存在某个项目,然后在必要时添加它。

否则,您将面临与使用常规字典相同的问题:另一个线程可能会在您检查存在之后但插入之前将条目添加到 SomeClasses

【讨论】:

    【解决方案3】:

    您在 https://gist.github.com/3500955 使用 ConcurrentDictionary 和 Lazy 的示例代码不正确 - 您正在编写:

        private static readonly ConcurrentDictionary<int, PrivateClass> SomeClasses =
            new ConcurrentDictionary<int, PrivateClass>();
        public SomeClass(int cachedInstanceId)
        {
            _privateClass = SomeClasses.GetOrAdd(cachedInstanceId, (key) => new Lazy<PrivateClass>(() => new PrivateClass(key)).Value);
        }
    

    ..应该是:

        private static readonly ConcurrentDictionary<int, Lazy<PrivateClass>> SomeClasses =
            new ConcurrentDictionary<int, Lazy<PrivateClass>>();
        public SomeClass(int cachedInstanceId)
        {
            _privateClass = SomeClasses.GetOrAdd(cachedInstanceId, (key) => new Lazy<PrivateClass>(() => new PrivateClass(key))).Value;
        }
    

    你需要使用 ConcurrentDictionary>,而不是 ConcurrentDictionary。 关键是您只能在从 GetOrAdd() 返回正确的 Lazy 对象之后访问 Lazy 的值 - 将 Lazy 对象的值发送到 GetOrAdd 函数会破坏整个目的使用它。

    编辑:啊 - 你在 https://gist.github.com/mennankara/3501446 找到了它:)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-08-24
      • 1970-01-01
      • 1970-01-01
      • 2020-11-29
      • 1970-01-01
      • 2014-08-06
      • 2010-12-24
      • 2011-01-26
      相关资源
      最近更新 更多