当它被清除时,我应该如何使我的哈希表缓存实现线程安全?

时间:2009-10-12 11:59:46

标签: c# concurrency locking

我有一个用于缓存对数据库资源的访问的类。它看起来像这样:

//gets registered as a singleton
class DataCacher<T>
{
    IDictionary<string, T> items = GetDataFromDb();

    //Get is called all the time from zillions of threads
    internal T Get(string key)
    {
         return items[key];
    }

    IDictionary<string, T> GetDataFromDb() { ...expensive slow SQL access... }

    //this gets called every 5 minutes
    internal void Reset()
    {
          items.Clear();
    }
}

我稍微简化了这段代码,但其中的要点是存在潜在的并发问题,因为在清除项目时,如果调用Get,则可能会出错。

现在我可以将锁定阻塞到Get和Reset中,但是我担心Get上的锁会降低站点的性能,因为Get会被Web应用程序中的每个请求线程多次调用。

我认为我可以使用双重检查的锁来做一些事情,但我怀疑使用比lock {}块更聪明的东西有更简洁的方法。怎么办?

编辑:抱歉所有,我之前没有明确说明,但我使用的items.Clear()实现实际上并不是一个直字典。它是ResourceProvider的包装器,它要求字典实现在每个项目被删除时调用.ReleaseAllResources()。这意味着调用代码不希望针对处置中的旧版本运行。鉴于此,Interlocked.Exchange方法是正确的吗?

3 个答案:

答案 0 :(得分:6)

我只需lock进行测试即可启动;在没有争议的情况下,锁很便宜。但是 - 更简单的方案是依赖于参考更新的原子性质:

public void Clear() {
    var tmp = GetDataFromDb(); // or new Dictionary<...> for an empty one
    items = tmp; // this is atomic; subsequent get/set will use this one
}

您可能还想让items成为volatile字段,只是为了确保它不会保存在任何地方的寄存器中。

这仍然存在这样的问题:任何期望某个给定密钥的人可能会感到失望(通过例外),但这是一个单独的问题。

更精细的选项可能是ReaderWriterLockSlim

答案 1 :(得分:2)

一种选择是完全替换IDictionary实例而不是清除它。您可以使用Exchange类上的Interlocked方法以线程安全的方式执行此操作。

答案 2 :(得分:0)

查看数据库是否会告诉您哪些数据发生了变化。你可以用

  • 触发将更改写入历史记录表
  • Query Notifications(SqlServer和Oracle有这些,其他也必须这样做)

因此您无需根据计时器重新加载所有数据。


失败了。

我会通过调用 GetDataFromDB()使Clear方法创建 new IDictionary ,然后在加载数据后设置“ items“字段指向新词典。 (一旦没有线程访问它,垃圾收集器将清理旧字典。)

  

我认为你不关心某些线程   重新加载时获得“旧”结果   数据 - (如果你这样做,你就会   必须阻止锁上的所有线程 -   痛苦!)

如果您需要所有线程同时切换到新词典,那么您需要将“items”字段声明为 volatile 并使用 Interlocked 类上的 Exchange 方法。但是现实生活中你不太可能需要它。