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