【发布时间】:2015-10-20 09:04:03
【问题描述】:
我有一个缓存(由 Web 应用程序使用),它在内部使用两个缓存 - 一个短期缓存,仅在请求中使用,一个长期缓存“永久”使用(跨请求) .
我有以下代码,请注意所有底层数据结构都是线程安全的。
public TCache Get(CacheDependency cachdeDependancy, Func<CacheDependency, TCache> cacheItemCreatorFunc)
{
TCache cacheItem;
if (shortTermCache.TryGetValue(cachdeDependancy.Id, out cacheItem))
{
return cacheItem;
}
DateTime cacheDependancyLastModified;
if (longTermCache.TryGetValue(cachdeDependancy.Id, out cacheItem)
&& IsValid(cachdeDependancy, cacheItem, out cacheDependancyLastModified))
{
cacheItem.CacheTime = cacheDependancyLastModified;
shortTermCache[cachdeDependancy.Id] = cacheItem;
return cacheItem;
}
cacheItem = cacheItemCreatorFunc(cachdeDependancy);
longTermCache.Add(cachdeDependancy.Id, cacheItem);
shortTermCache[cachdeDependancy.Id] = cacheItem;
return cacheItem;
}
显然,当运行并发(即多个 Web 请求)时,上面的代码仍然有可能(甚至可能)不一致。 但是我写了一些单元测试,我看到的是从来没有发生过“异常”。可能发生的情况是再次添加相同的项目,即使它已经存在等等。--> 我想你可以在查看代码时明白我的意思。
我仍然认为拥有一个始终正确且一致的解决方案会很好。
所以我使用简单的双重检查锁定机制重写了这段代码(也许这可能会更好,通过为另一个缓存添加另一个/第二个锁定?):
public TCache Get(CacheDependency cachdeDependancy, Func<CacheDependency, TCache> cacheItemCreatorFunc)
{
TCache cacheItem;
if (shortTermCache.TryGetValue(cachdeDependancy.Id, out cacheItem))
{
return cacheItem;
}
lock (_lockObj)
{
if (shortTermCache.TryGetValue(cachdeDependancy.Id, out cacheItem))
{
return cacheItem;
}
DateTime cacheDependancyLastModified;
if (longTermCache.TryGetValue(cachdeDependancy.Id, out cacheItem)
&& IsValid(cachdeDependancy, cacheItem, out cacheDependancyLastModified))
{
cacheItem.CacheTime = cacheDependancyLastModified;
shortTermCache[cachdeDependancy.Id] = cacheItem;
return cacheItem;
}
cacheItem = cacheItemCreatorFunc(cachdeDependancy);
longTermCache.Add(cachdeDependancy.Id, cacheItem);
shortTermCache[cachdeDependancy.Id] = cacheItem;
return cacheItem;
}
}
我认为这段代码现在可以在多线程环境中正常工作。
但我不确定的是: 这会不会非常慢,因此也会破坏缓存的目的?忍受这个问题会更好吗,缓存有时会出现“不一致”的行为? 因为如果同时有1000个web请求,都得等到能进入lock zone。或者这根本不是一个真正的问题,因为 CPU 一次只有特定数量的内核(因此是“真正的”并行线程),而且这种性能损失总是很小的?
【问题讨论】:
-
这很大程度上取决于 2 个想法:当前有多少线程访问您的代码,以及在 lock 语句中运行代码需要多长时间。只要它只访问内存(没有数据库,没有磁盘)并且你调用的方法很快,它应该不是一个真正的问题。 (如果您的第 1000 个请求需要等待一毫秒左右,这不会成为瓶颈)但同样,它取决于实际情况。
-
您可以使用Amdahls law计算并行化的改进。
-
只能同时创建一个缓存项,这似乎是一个杀手。
标签: c# multithreading locking