在每个项目基础上同步线程
Synchronize threads on per-item base
虽然这个问题是关于 MemoryCache
class,但我可以想象 Dictionary
或 ConcurrentDictionary.GetOrAdd
的相同需求,其中 valueFactory
-lambda也是一个冗长的操作。
本质上,我想 synchronize/lock 基于每个项目的线程。我知道 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 命名信号量。
我确定我不是第一个需要这种同步的人,但我没有找到任何适合这种情况的question/answer。
我的问题是,是否有人可以针对此问题提出一种不同的、资源友好的方法?
更新
我发现 this NamedReaderWriterLocker
class 一开始看起来很有前途,但使用起来很危险,因为两个线程可能会获得同名的不同 ReaderWriterLockSlim
实例同时进入 ConcurrentDictionary
的 valueFactory
。也许我可以在 GetLock
方法中使用这个实现和一些额外的锁。
由于您的密钥是一个字符串,您可以锁定 string.Intern(id)
。
MSDN 文档:System.String.Intern
即
lock (string.Intern(id))
{
var item = cache.GetCachedItem(id);
if (item != null)
{
return item.Value;
}
cache.Add(id, this.CreateItem(id), new CacheItemPolicy
{
SlidingExpiration = TimeSpan.FromMinutes(20)
});
return /* some value, this line was absent in the original code. */;
}
虽然这个问题是关于 MemoryCache
class,但我可以想象 Dictionary
或 ConcurrentDictionary.GetOrAdd
的相同需求,其中 valueFactory
-lambda也是一个冗长的操作。
本质上,我想 synchronize/lock 基于每个项目的线程。我知道 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 命名信号量。
我确定我不是第一个需要这种同步的人,但我没有找到任何适合这种情况的question/answer。
我的问题是,是否有人可以针对此问题提出一种不同的、资源友好的方法?
更新
我发现 this NamedReaderWriterLocker
class 一开始看起来很有前途,但使用起来很危险,因为两个线程可能会获得同名的不同 ReaderWriterLockSlim
实例同时进入 ConcurrentDictionary
的 valueFactory
。也许我可以在 GetLock
方法中使用这个实现和一些额外的锁。
由于您的密钥是一个字符串,您可以锁定 string.Intern(id)
。
MSDN 文档:System.String.Intern
即
lock (string.Intern(id))
{
var item = cache.GetCachedItem(id);
if (item != null)
{
return item.Value;
}
cache.Add(id, this.CreateItem(id), new CacheItemPolicy
{
SlidingExpiration = TimeSpan.FromMinutes(20)
});
return /* some value, this line was absent in the original code. */;
}