锁定自加载缓存

问题描述:

我正在C#中实现一个简单的缓存,并试图使其可以从多个线程访问。在基本阅读的情况下,很容易:锁定自加载缓存

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但在此之前它是完全填充,如果另一个线程是填充它的过程。 那么在cacheB上执行锁定以便缓存是线程安全的最佳方式是什么?

可以使用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挤出更多的性能允许,那么你可以使用一个ReaderWriterLockSlim代替,这将允许多个线程读取字典同时。当您需要填充或更新字典时,可以将锁升级到写入模式。

像这样: http://devplanet.com/blogs/brianr/archive/2008/09/26/thread-safe-dictionary-in-net.aspx

你的第一个例子是不是在所有线程安全的。如果有人插入另一个人从地图上取东西时该怎么办?插入可能会改变字典,这意味着阅读它可以访问损坏的状态

您可以使用ReaderWriterLock

您可以使用线程安全的Enterprise Library Cache。

在监视器中包装填充操作。这样,如果另一个线程在填充缓存时尝试读取缓存,那么在它尝试读取之前,线程将*等待填充操作完成/错误。

您也可以做到这一点的读取停止,而你从中读取被修改的高速缓存,在这种情况下,你需要读入一个临时变量,释放监视器,然后返回变量或你会遇到锁定问题。

public MyObj GetCachedObjB(int key) 
{ 
    try { 
     Monitor.Enter(cacheB); 

     if (cacheB == null) 
     { 
      PopulateCacheB(); 
     } 
    } finally { 
     Monitor.Exit(cacheB); 
    } 
    return cacheB[key]; 
} 

作为一般记它,你不妨做时添加一些关键的验证读取你的字典,虽然这取决于你是否要对不存在的密钥或一些默认值的误差。

+0

任何不使用“锁定”语句而不是所有“尝试...输入...终于...退出”的东西的原因? http://msdn.microsoft.com/en-us/library/c5kehkcz.aspx – LukeH 2009-06-30 17:09:59

假设你的词典是线程安全的或只读人口后,你可以在这样一个线程安全的方式来填充它:

private void PopulateCacheB() 
{  
    Dictionary<int, MyObj>() dictionary = new Dictionary<int, MyObj>();  
    foreach (MyObj item in databaseAccessor)  
    {   
     dictionary.Add(item.Key, item);  
    } 
    cacheB = dictionary; 
} 

在最坏情况下的数据将来自“databaseAccessor”检索超过一旦出现竞赛状况,但这不应该受到伤害。