【发布时间】:2016-04-25 11:22:39
【问题描述】:
虽然这个问题是关于 MemoryCache 类的,但我可以想象与 Dictionary 或 ConcurrentDictionary.GetOrAdd 相同的需求,其中 valueFactory-lambda 也是一个冗长的操作。
本质上,我想在每个项目的基础上同步/锁定线程。我知道MemoryCache 是线程安全的,但是仍然需要检查一个项目是否存在并在它不存在时添加该项目,仍然需要同步。
考虑这个示例代码:
public class MyCache
{
private static readonly MemoryCache cache = new MemoryCache(Guid.NewGuid().ToString());
public object Get(string id)
{
var cacheItem = cache.GetCachedItem(id);
if (cacheItem != null) return cacheItem.Value;
var item = this.CreateItem(id);
cache.Add(id, item, new CacheItemPolicy
{
SlidingExpiration = TimeSpan.FromMinutes(20)
});
return item;
}
private object CreateItem(string id)
{
// Lengthy operation, f.e. querying database or even external API
return whateverCreatedObject;
}
}
如您所见,我们需要同步cache.GetCachedItem 和cache.Add。但是由于CreateItem 是一个冗长的操作(因此是MemoryCache),我不想像这段代码那样锁定所有线程:
public object Get(string id)
{
lock (cache)
{
var item = cache.GetCachedItem(id);
if (item != null) return item.Value;
cache.Add(id, this.CreateItem(id), new CacheItemPolicy
{
SlidingExpiration = TimeSpan.FromMinutes(20)
});
}
}
此外,没有锁也不是一种选择,因为这样我们可以有多个线程为同一个id 调用CreateItem。
我可以做的是为每个 id 创建一个名为 Semaphore 的唯一名称,因此锁定是在每个项目的基础上发生的。但这将是一个系统资源杀手,因为我们不想在我们的系统上注册 +100K 命名信号量。
我确定我不是第一个需要这种同步的人,但我没有找到任何适合这种情况的问题/答案。
我的问题是,是否有人可以为这个问题想出一种不同的、资源友好的方法?
更新
我发现this NamedReaderWriterLocker 类起初看起来很有希望,但使用起来很危险,因为当两个线程都进入ConcurrentDictionary 的@987654341 时,两个线程可能会获得同名的不同ReaderWriterLockSlim 实例@ 同时。也许我可以在GetLock 方法中使用这个实现和一些额外的锁。
【问题讨论】:
-
ConcurrentDictionary在其 API 中使用Try*/*Or*模式:msdn.microsoft.com/en-us/library/dd287191%28v=vs.110%29.aspx -
是的,
GetOrAdd有点像,但我敢打赌ConcurrentDictionary在执行 add lambdavalueFactory时也会锁定整个字典,并且不使用每键锁定。 -
AFAIK,
ConcurrentDictionary是无锁的。 -
经过测试,确实是无锁的。测试表明,第二个
GetOrAdd的valueFactory也被调用,而第一个GetOrAdd的valueFactory仍在进行中,即使我们正在尝试获取相同的项目(相同的密钥)。所以,我想要的是第二个GetOrAdd调用等待第一个调用完成(但前提是我们当然要尝试获取相同的项目)。 -
“等待”意味着一些锁定。无需等待,就有可能调用 value factory 两次,你是绝对正确的。恐怕,无论实施如何,您都必须在这两种情况之间做出选择。
标签: c# multithreading synchronization locking semaphore