从具有相同键/值的多个线程更新Dictionary是否安全

时间:2010-11-24 06:16:30

标签: .net collections concurrency thread-safety

我有一个用作缓存的静态System.Collections.Generic.Dictionary<K, V>。在缓存未命中时,我做了一些昂贵的工作来获取指定密钥的值并将其保存在缓存中:

private static Dictionary<K, V> _cache = ...

public static V GetValue<K>(K key)
{
    V value;
    if (!_cache.TryGetValue(key, out value))
    {
         value = GetExpensiveValue(key);
         _cache[key] = value; // Thread-safety issue?
    }

    return value;
}

这是一种常见的模式。问题是:当多个线程试图同时调用GetValue()时,这是安全还是失败?

假设GetExpensiveValue总是返回给定键的相同值,我并不关心哪个线程赢得竞争条件。是的,这意味着昂贵的操作可能比需要的时间多一次,但这是一个权衡我可能会感到高兴,如果读取比写入更频繁,这可以让我消除锁定。但是在字典里面会出现什么问题吗?也就是说,它会在某些情况下抛出异常还是破坏其数据?我用一个简单的控制台应用程序对此进行了测试,发现没有任何问题,但当然这并不意味着它总会有效。

2 个答案:

答案 0 :(得分:1)

这对我来说看起来不安全;字典内部可能已经在重写过程中。在最小,它可能需要读取器/写入器锁:

private readonly static ReaderWriterLockSlim @lock = new ReaderWriterLockSlim();
public static V GetValue(K key)
{
    V value;
    @lock.EnterReadLock();
    bool hasValue;
    try
    {
        hasValue = _cache.TryGetValue(key, out value);
    }
    finally
    {
        @lock.ExitReadLock();
    }

    if (!hasValue)
    {
        value = GetExpensiveValue(key);
        @lock.EnterWriteLock();
        try
        {
            _cache[key] = value;
        }
        finally
        {
            @lock.ExitWriteLock();
        }
    }
    return value;
}

如果您只希望GetExpensiveValue(key)执行一次,请将其移动到里面写入锁定并添加一个双重检查:

        @lock.EnterWriteLock();
        try
        {
            if(!_cache.TryGetValue(key, out value)) { // double-check
                value = GetExpensiveValue(key);
                _cache[key] = value;
            }
        }
        finally
        {
            @lock.ExitWriteLock();
        }

答案 1 :(得分:0)

如果你不关心比赛条件那么你应该没事。如果您关心昂贵的操作,请在lock之后TryGetvalue()然后再次致电TryGetValue()

private static Dictionary<K, V> _cache = ...
private static object _lockObject = new object();

public static V GetValue<K>(K key)
{
    V value;
    if (!_cache.TryGetValue(key, out value))
    {
        lock(_lockObject)
        {
            if (!_cache.TryGetValue(key, out value))
            {
                 value = GetExpensiveValue(key);
                 _cache[key] = value; // Thread-safety issue?
            }
        }
    }

    return value;
}

修改 在考虑了Marc的评论后,我认为以上不是最好的选择。也许考虑使用ConcurrentDictionary代替。