6

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

简单地说,当我用 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 的建议下,尝试使用ConcurrentDictionary 的GetOrAdd()方法和Lazy的组合,但不幸的是,构造函数PrivateClass仍然调用了不止一次。测试代码见https://gist.github.com/3500955

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

4

3 回答 3

12

你在滥用ConcurrentDictionary.

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

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

ConcurrentDictionary专为这种情况而设计。

你应该简单地打电话

 _privateClass = SomeClasses.GetOrAdd(cachedInstanceId, key => new PrivateClass());
于 2012-08-28T15:59:23.487 回答
5

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

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

于 2012-08-28T16:02:30.557 回答
3

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

    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<TKey, Lazy<TVal>>,而不是 ConcurrentDictionary<TKey, TVal>。关键是您只有在从 GetOrAdd() 返回正确的 Lazy 对象才能访问 Lazy的值 - 将 Lazy 对象的值发送到 GetOrAdd 函数会破坏使用它的整个目的。

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

于 2015-01-12T12:49:31.687 回答