1

我在 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 上执行锁定以使缓存是线程安全的最佳方法是什么?

4

6 回答 6

2

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

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

于 2009-06-29T20:31:09.573 回答
1

您可以使用ReaderWriterLock

于 2009-06-29T20:32:00.833 回答
1

将填充操作包装在监视器中。这样,如果另一个线程在填充缓存时尝试读取缓存,则线程将被迫等待填充操作完成/出错,然后才能尝试读取。

您可能还希望对读取执行此操作以在您读取缓存时停止更改缓存,在这种情况下,您需要读入一个临时变量,释放监视器然后返回该变量,否则您将运行进入锁定问题。

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

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

作为一般说明,您可能希望在对字典进行读取时添加一些键验证,但这取决于您是否希望在不存在的键或某些默认值上出现错误。

于 2009-06-29T20:35:48.660 回答
1

假设您的字典在填充后是线程安全的或只读的,您可以以线程安全的方式填充它:

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

在最坏的情况下,如果存在竞争条件,将多次从“databaseAccessor”中检索数据,但这不应该受到伤害。

于 2009-06-29T20:57:57.180 回答
1

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

于 2009-06-29T22:04:18.183 回答
0

您可以使用线程安全的企业库缓存。

于 2009-06-29T20:32:29.540 回答