在此ConcurrentDictionary缓存方案中是否需要锁定

时间:2012-08-28 15:58:00

标签: c# multithreading concurrency

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

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

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之后,尝试使用GetOrAdd()Lazy方法,但遗憾的是,PrivateClass的构造函数仍然被调用了多次。有关测试代码,请参阅https://gist.github.com/3500955

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

3 个答案:

答案 0 :(得分:9)

你误用了ConcurrentDictionary

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

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

ConcurrentDictionary旨在用于此类场景。

您应该致电

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

答案 1 :(得分:4)

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

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

答案 2 :(得分:2)

使用ConcurrentDictionary和Lazy&lt; T&gt;的https://gist.github.com/3500955示例代码是不正确的 - 你在写:

    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&lt; TKey,Lazy&lt; TVal&gt;&gt;,而不是ConcurrentDictionary&lt; TKey,TVal&gt;。 关键是你只能访问Lazy 的值从GetOrAdd()返回正确的Lazy对象 - 将Lazy对象的值发送到GetOrAdd函数会失败的全部目的使用它。

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