C# 锁定自加载缓存
我正在用C实现一个简单的缓存,并试图从多个线程访问它。在基本阅读案例中,很容易:C# 锁定自加载缓存,c#,multithreading,locking,thread-safety,C#,Multithreading,Locking,Thread Safety,我正在用C实现一个简单的缓存,并试图从多个线程访问它。在基本阅读案例中,很容易: var cacheA = new Dictionary<int, MyObj>(); // Populated in constructor public MyObj GetCachedObjA(int key) { return cacheA[key]; } 我相信这是完全线程安全的。但我也想让它自动加载。也就是说,如果缓存在访问时未填充,则会在满足访问请求之前填充缓存 Dictionar
var cacheA = new Dictionary<int, MyObj>(); // Populated in constructor
public MyObj GetCachedObjA(int key)
{
return cacheA[key];
}
我相信这是完全线程安全的。但我也想让它自动加载。也就是说,如果缓存在访问时未填充,则会在满足访问请求之前填充缓存
Dictionary<int, MyObj> cacheB = null;
public MyObj GetCachedObjB(int key)
{
if (cacheB == null)
{
PopulateCacheB();
}
return cacheB[key];
}
private void PopulateCacheB()
{
cacheB = new Dictionary<int, MyObj>();
foreach (MyObj item in databaseAccessor)
{
cacheB.Add(item.Key, item);
}
}
这不是线程安全的,因为如果另一个线程正在填充它,那么一个线程可以在实例化cacheB之后但在完全填充它之前访问GetCachedObjB。
那么,在缓存B上执行锁定以使缓存线程安全的最佳方法是什么呢?如下所示:
您的第一个示例根本不是线程安全的。如果有人在另一个人从地图上取东西的时候插入怎么办?插入可能会改变字典,这意味着读取字典可能会访问损坏状态,如下所示:
您的第一个示例根本不是线程安全的。如果有人在另一个人从地图上取东西的时候插入怎么办?插入可能会改变字典,这意味着读取字典可能会访问损坏状态您可以使用一个新的字典 你可以用一个 您可以使用企业库缓存,它是线程安全的 您可以使用企业库缓存,它是线程安全的 将填充操作包装在监视器中。这样,如果另一个线程在填充缓存时尝试读取缓存,则该线程将被强制等待,直到填充操作完成/出错,然后才能尝试读取 您可能还希望对读取执行此操作,以在读取缓存时停止更改缓存。在这种情况下,您需要读取一个临时变量,释放监视器,然后返回该变量,否则将遇到锁定问题
public MyObj GetCachedObjB(int key)
{
try {
Monitor.Enter(cacheB);
if (cacheB == null)
{
PopulateCacheB();
}
} finally {
Monitor.Exit(cacheB);
}
return cacheB[key];
}
一般来说,在对字典进行读取时,您可能希望添加一些键验证,但这取决于您是希望在不存在的键上出错还是希望在某些默认值上出错。将填充操作包装在监视器中。这样,如果另一个线程在填充缓存时尝试读取缓存,则该线程将被强制等待,直到填充操作完成/出错,然后才能尝试读取 您可能还希望对读取执行此操作,以在读取缓存时停止更改缓存。在这种情况下,您需要读取一个临时变量,释放监视器,然后返回该变量,否则将遇到锁定问题
public MyObj GetCachedObjB(int key)
{
try {
Monitor.Enter(cacheB);
if (cacheB == null)
{
PopulateCacheB();
}
} finally {
Monitor.Exit(cacheB);
}
return cacheB[key];
}
一般来说,在对字典执行读取操作时,您可能希望添加一些键验证,但这取决于您是否希望在不存在的键或某些默认值上出现错误。假设您的字典在填充后是线程安全的或只读的,您可以以线程安全的方式填充它,因此:
private void PopulateCacheB()
{
Dictionary<int, MyObj>() dictionary = new Dictionary<int, MyObj>();
foreach (MyObj item in databaseAccessor)
{
dictionary.Add(item.Key, item);
}
cacheB = dictionary;
}
在最坏的情况下,如果存在争用条件,将从databaseAccessor多次检索数据,但这不会造成任何伤害。假设您的字典在填充后是线程安全的或只读的,您可以以线程安全的方式填充它,因此:
private void PopulateCacheB()
{
Dictionary<int, MyObj>() dictionary = new Dictionary<int, MyObj>();
foreach (MyObj item in databaseAccessor)
{
dictionary.Add(item.Key, item);
}
cacheB = dictionary;
}
在最坏的情况下,如果存在争用条件,则会多次从databaseAccessor检索数据,但这不会造成任何伤害。为了简单的线程安全,您可以使用以下语句:
private Dictionary<int, MyObj> cacheB = null;
private readonly object cacheLockB = new object();
public MyObj GetCachedObjB(int key)
{
lock (cacheLockB)
{
if (cacheB == null)
{
Dictionary<int, MyObj> temp = new Dictionary<int, MyObj>();
foreach (MyObj item in databaseAccessor)
{
temp.Add(item.Key, item);
}
cacheB = temp;
}
return cacheB[key];
}
}
如果您需要挤出比lock允许的性能更高的性能,那么您可以使用,这将允许多个线程同时从字典中读取。当您需要填充或更新字典时,您可以将锁升级为写入模式。您可以使用该语句来实现简单的线程安全:
private Dictionary<int, MyObj> cacheB = null;
private readonly object cacheLockB = new object();
public MyObj GetCachedObjB(int key)
{
lock (cacheLockB)
{
if (cacheB == null)
{
Dictionary<int, MyObj> temp = new Dictionary<int, MyObj>();
foreach (MyObj item in databaseAccessor)
{
temp.Add(item.Key, item);
}
cacheB = temp;
}
return cacheB[key];
}
}
如果您需要挤出比lock允许的性能更高的性能,那么您可以使用,这将允许多个线程同时从字典中读取。当您需要填充或更新字典时,您可以将锁定升级为写入模式。有没有理由不使用lock语句而不是尝试…输入…最后…退出?主要是因为它明确说明了使用监视器的意义,监视器是并发编程中的一个关键概念。此外,此模式适用于任何.Net语言。通过c lock关键字或vb synclock关键字的用法是隐式的,就我个人而言,我使用这种形式是因为在我的代码中,我经常采取一些可能失败并引发异常的操作,因此我有一个Catch块来执行适当的日志记录/清理,finally块确保我不会因为异常而使某些资源死锁任何不使用lock语句的原因尝试…进入…最后…退出?主要是因为它明确说明了使用监视器的意义,监视器是并发编程中的一个关键概念。此外,此模式适用于任何.Net语言。通过c lock关键字或vb synclock关键字的用法是隐式的。我个人有另一个想法,我使用这种形式,因为在我的代码中,我经常采取一些可能失败并引发异常的操作,所以我 我创建了一个Catch块来执行适当的日志记录/清理,finally块确保我不会因为异常而死锁某些资源