C# 正在寻找一种在缓存时减少锁定的方法
我正在使用下面的代码来缓存项目。这很基本 我遇到的问题是,每次它缓存一个项目时,代码部分都会锁定。因此,大约每小时有一百万件物品到达,这是一个问题 我试着为每个cacheKey创建一个静态锁对象的字典,这样锁定是细粒度的,但这本身就成了管理它们过期的问题,等等 有没有更好的方法来实现最小锁定C# 正在寻找一种在缓存时减少锁定的方法,c#,.net,.net-core,C#,.net,.net Core,我正在使用下面的代码来缓存项目。这很基本 我遇到的问题是,每次它缓存一个项目时,代码部分都会锁定。因此,大约每小时有一百万件物品到达,这是一个问题 我试着为每个cacheKey创建一个静态锁对象的字典,这样锁定是细粒度的,但这本身就成了管理它们过期的问题,等等 有没有更好的方法来实现最小锁定 private static readonly object cacheLock = new object(); public static T GetFromCache<T>(string c
private static readonly object cacheLock = new object();
public static T GetFromCache<T>(string cacheKey, Func<T> GetData) where T : class {
// Returns null if the string does not exist, prevents a race condition
// where the cache invalidates between the contains check and the retrieval.
T cachedData = MemoryCache.Default.Get(cacheKey) as T;
if (cachedData != null) {
return cachedData;
}
lock (cacheLock) {
// Check to see if anyone wrote to the cache while we where
// waiting our turn to write the new value.
cachedData = MemoryCache.Default.Get(cacheKey) as T;
if (cachedData != null) {
return cachedData;
}
// The value still did not exist so we now write it in to the cache.
cachedData = GetData();
MemoryCache.Default.Set(cacheKey, cachedData, new CacheItemPolicy(...));
return cachedData;
}
}
您可能想考虑使用,只有在需要时才能获得写锁。
使用cacheLock.entereadlock;和cacheLock.EnterWriteLock;应该大大提高性能 我给出的链接甚至有一个缓存示例,正是您需要的,我复制到这里:public class SynchronizedCache
{
private ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim();
private Dictionary<int, string> innerCache = new Dictionary<int, string>();
public int Count
{ get { return innerCache.Count; } }
public string Read(int key)
{
cacheLock.EnterReadLock();
try
{
return innerCache[key];
}
finally
{
cacheLock.ExitReadLock();
}
}
public void Add(int key, string value)
{
cacheLock.EnterWriteLock();
try
{
innerCache.Add(key, value);
}
finally
{
cacheLock.ExitWriteLock();
}
}
public bool AddWithTimeout(int key, string value, int timeout)
{
if (cacheLock.TryEnterWriteLock(timeout))
{
try
{
innerCache.Add(key, value);
}
finally
{
cacheLock.ExitWriteLock();
}
return true;
}
else
{
return false;
}
}
public AddOrUpdateStatus AddOrUpdate(int key, string value)
{
cacheLock.EnterUpgradeableReadLock();
try
{
string result = null;
if (innerCache.TryGetValue(key, out result))
{
if (result == value)
{
return AddOrUpdateStatus.Unchanged;
}
else
{
cacheLock.EnterWriteLock();
try
{
innerCache[key] = value;
}
finally
{
cacheLock.ExitWriteLock();
}
return AddOrUpdateStatus.Updated;
}
}
else
{
cacheLock.EnterWriteLock();
try
{
innerCache.Add(key, value);
}
finally
{
cacheLock.ExitWriteLock();
}
return AddOrUpdateStatus.Added;
}
}
finally
{
cacheLock.ExitUpgradeableReadLock();
}
}
public void Delete(int key)
{
cacheLock.EnterWriteLock();
try
{
innerCache.Remove(key);
}
finally
{
cacheLock.ExitWriteLock();
}
}
public enum AddOrUpdateStatus
{
Added,
Updated,
Unchanged
};
~SynchronizedCache()
{
if (cacheLock != null) cacheLock.Dispose();
}
}
您可能想考虑使用,只有在需要时才能获得写锁。
使用cacheLock.entereadlock;和cacheLock.EnterWriteLock;应该大大提高性能 我给出的链接甚至有一个缓存示例,正是您需要的,我复制到这里:public class SynchronizedCache
{
private ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim();
private Dictionary<int, string> innerCache = new Dictionary<int, string>();
public int Count
{ get { return innerCache.Count; } }
public string Read(int key)
{
cacheLock.EnterReadLock();
try
{
return innerCache[key];
}
finally
{
cacheLock.ExitReadLock();
}
}
public void Add(int key, string value)
{
cacheLock.EnterWriteLock();
try
{
innerCache.Add(key, value);
}
finally
{
cacheLock.ExitWriteLock();
}
}
public bool AddWithTimeout(int key, string value, int timeout)
{
if (cacheLock.TryEnterWriteLock(timeout))
{
try
{
innerCache.Add(key, value);
}
finally
{
cacheLock.ExitWriteLock();
}
return true;
}
else
{
return false;
}
}
public AddOrUpdateStatus AddOrUpdate(int key, string value)
{
cacheLock.EnterUpgradeableReadLock();
try
{
string result = null;
if (innerCache.TryGetValue(key, out result))
{
if (result == value)
{
return AddOrUpdateStatus.Unchanged;
}
else
{
cacheLock.EnterWriteLock();
try
{
innerCache[key] = value;
}
finally
{
cacheLock.ExitWriteLock();
}
return AddOrUpdateStatus.Updated;
}
}
else
{
cacheLock.EnterWriteLock();
try
{
innerCache.Add(key, value);
}
finally
{
cacheLock.ExitWriteLock();
}
return AddOrUpdateStatus.Added;
}
}
finally
{
cacheLock.ExitUpgradeableReadLock();
}
}
public void Delete(int key)
{
cacheLock.EnterWriteLock();
try
{
innerCache.Remove(key);
}
finally
{
cacheLock.ExitWriteLock();
}
}
public enum AddOrUpdateStatus
{
Added,
Updated,
Unchanged
};
~SynchronizedCache()
{
if (cacheLock != null) cacheLock.Dispose();
}
}
我不知道MemoryCache.Default是如何实现的,也不知道您是否可以控制它。
但一般来说,在多线程环境中,更喜欢使用ConcurrentDictionary而不是带锁的Dictionary
GetFromCache将成为
ConcurrentDictionary<string, T> cache = new ConcurrentDictionary<string, T>();
...
cache.GetOrAdd("someKey", (key) =>
{
var data = PullDataFromDatabase(key);
return data;
});
并将缓存存储为具有定义到期日的CacheItem
cache.GetOrAdd("someKey", (key) =>
{
var data = PullDataFromDatabase(key);
return new CacheItem<T>() { Item = data, Expiry = DateTime.UtcNow.Add(TimeSpan.FromHours(1)) };
});
解释
在同时请求的情况下,使用GetOrAdd,如果不在缓存中,您可能会多次调用get from数据库委托。
但是,GetOrAdd最终将只使用委托返回的一个值,通过返回一个Lazy,您可以保证只调用一个Lazy。我不知道MemoryCache.Default是如何实现的,或者您是否可以控制它。
但一般来说,在多线程环境中,更喜欢使用ConcurrentDictionary而不是带锁的Dictionary
GetFromCache将成为
ConcurrentDictionary<string, T> cache = new ConcurrentDictionary<string, T>();
...
cache.GetOrAdd("someKey", (key) =>
{
var data = PullDataFromDatabase(key);
return data;
});
并将缓存存储为具有定义到期日的CacheItem
cache.GetOrAdd("someKey", (key) =>
{
var data = PullDataFromDatabase(key);
return new CacheItem<T>() { Item = data, Expiry = DateTime.UtcNow.Add(TimeSpan.FromHours(1)) };
});
解释
在同时请求的情况下,使用GetOrAdd,如果不在缓存中,您可能会多次调用get from数据库委托。
但是,GetOrAdd最终将只使用委托返回的一个值,通过返回一个Lazy值,您可以保证只调用一个Lazy值。您说每小时有一百万个缓存请求,但您创建新缓存的频率是多少?如果您只有5个缓存,并且平均每30分钟清除一次,那么您的锁定基本上不会占用任何开销。另一方面,如果每30秒填充10个缓存,那么锁定策略将增加大量开销。它还关系到您从未填充的不同缓存请求项目的频率。如果数量很多,你不应该这样做,如果不是,这不太可能成为瓶颈。@Servy百万新项目在一个小时内到达。因此,将建立一个锁,获取项目,并将其添加到缓存中。您说您每小时有一百万个缓存请求,但您创建新缓存的频率是多少?如果您只有5个缓存,并且平均每30分钟清除一次,那么您的锁定基本上不会占用任何开销。另一方面,如果每30秒填充10个缓存,那么锁定策略将增加大量开销。它还关系到您从未填充的不同缓存请求项目的频率。如果数量很多,你不应该这样做,如果不是,这不太可能成为瓶颈。@Servy百万新项目在一个小时内到达。因此,将建立锁、获取项并将其添加到缓存中。您现在不会使任何内容过期,并且可能会多次加载缓存。OP的代码没有任何问题。使用Lazy的技巧有问题,或者已经在其他实现中出现,因为如果PullFromDatabase返回异常,它会将异常保存在缓存中。是吗?@AngryHacker你说得对,我还没想过。我想解决这个问题的一种方法是捕获延迟委托中的异常,并在catch块中将缓存设置为null。然后,检查检查是否为null。如果是这样,请从缓存中删除该项,并在需要时引发异常。您现在不会使任何内容过期,并且可能会多次加载缓存。OP的代码没有任何问题。使用Lazy的技巧有问题,或者已经在其他实现中出现,因为如果PullFromDatabase返回异常,它会将异常保存在缓存中。是吗?@AngryHacker你说得对,我还没想过。我想解决这个问题的一种方法是捕获延迟委托中的异常,并在catch块中将缓存设置为null。然后,检查检查是否为null。如果是这样,请从缓存中删除该项,并在需要时引发异常。