【发布时间】:2017-10-10 08:14:03
【问题描述】:
我处理多线程和缓存的标准方法是使用"Double-checked locking" 模式。在数据检索可能需要很长时间的情况下,这会导致后续线程在第一个线程刷新缓存时等待。如果请求的吞吐量比数据的新鲜度具有更高的优先级,我希望能够在刷新缓存时继续将陈旧的缓存数据提供给后续线程。
我在System.Runtime.Caching 中使用ObjectCache。放置在缓存中的项目有一个标志,指示数据是否过时。当项目过期并从缓存中删除时,我使用RemoveCallback 机制重新输入设置了过期标志的项目。
处理访问缓存的代码如下:
class Repository {
static ObjectCache Cache = MemoryCache.Default;
static readonly SemaphoreSlim RefreshCacheSemaphore = new SemaphoreSlim(1);
static volatile bool DataIsBeingRefreshed;
public async Task<object> GetData() {
const string cacheKey = "Key";
var cacheObject = Cache.Get(cacheKey) as CacheObject;
if(cacheObject != null && (!cacheObject.IsStale || DataIsBeingRefreshed)) {
return cacheObject.Item;
}
await RefreshCacheSemaphore.WaitAsync();
try {
// Check again that the cache item is still stale.
cacheObject = Cache.Get(cacheKey) as CacheObject;
if(cacheObject != null && !cacheObject.IsStale) {
return cacheObject.Item;
}
DataIsBeingRefreshed = true;
// Get data from database.
// Store new data in cache.
DataIsBeingRefreshed = false;
// Return new data.
} finally {
RefreshCacheSemaphore.Release();
}
}
}
这样做的问题是,根据调用之间的时间,线程要么成功地提供陈旧数据,要么陷入等待输入受信号量保护的代码。理想情况下,我不希望任何线程在缓存刷新时等待。
或者,我可以将方法更改为:
public async Task<object> GetData() {
const string cacheKey = "Key";
var cacheObject = Cache.Get(cacheKey) as CacheObject;
if(cacheObject != null && (!cacheObject.IsStale || DataIsBeingRefreshed)) {
return cacheObject.Item;
}
// New semaphore.
await GetStaleDataSemaphore.WaitAsync();
try {
cacheObject = Cache.Get(cacheKey) as CacheObject;
if(cacheObject != null && DataIsBeingRefreshed) {
return cacheObject.Item
}
DataIsBeingRefreshed = true;
} finally {
GetStaleDataSemaphore.Release();
}
await RefreshCacheSemaphore.WaitAsync();
try {
// Check again that the cache item is still stale.
cacheObject = Cache.Get(cacheKey) as CacheObject;
if(cacheObject != null && !cacheObject.IsStale)
{
return cacheObject.Item;
}
// Get data from database.
// Store new data in cache.
DataIsBeingRefreshed = false;
// Return new data.
}
finally
{
RefreshCacheSemaphore.Release();
}
}
这应该会减少等待刷新缓存的线程数量,但是,如果我错过了一些会导致没有线程等待的既定模式,我不想引入更多的锁定机制。
我是在正确的路线上还是有一个既定的模式来处理这个问题?
【问题讨论】:
-
我不懂 C#,所以无法写出答案。在 C++ 中,我会构建一个新缓存,并在它准备好时将其与旧缓存交换。有了这个,你只需要在交换时锁定,而不是在加载时锁定。
标签: c# multithreading caching design-patterns