如何正确锁定并发多个请求

How to properly lock with concurrent multiple requests

我的系统中有一个自动刷新缓存,由于竞争条件,运行 出现了一些问题。

在启动期间,作为并发字典的 _internalCache 为空。

这是几年前实现的,作为我们系统中使用的通用自动刷新缓存。

导致大部分问题的刷新操作从数据库中刷新了几千行。

public bool TryGet(TKey key, out TValue value)
{
    if (_internalCache.TryGetValue(key, out value))
    {
        return true;
    }
    lock (_internalCache.SyncRoot)
    {
        this._refreshCacheAction(this._internalCache);
        return _internalCache.TryGetValue(key, out value);
    }
}

如果多个请求同时进入(这种情况发生的频率比我希望的要高),那么我们会多次刷新缓存。

编辑: 经过评论的进一步讨论,看起来这个缓存被严重破坏了。我们的一些客户遇到超时问题,我需要快速修补程序。

如何防止多次刷新缓存? (欢迎 Jenky hack)

确保包含 TryGet 的 class 是跨所有调用的单例实例,因为它在应用程序生命周期内只应创建一次。私有实例构造函数与引用单个实例的 class 上的静态 属性 相结合,在 class 的静态构造函数中构造就足够了:

public class ASingletonClass
{
    static ASingletonClass()
    {
         Instance = new ASingletonClass();
    }

    private ASingletonClass()
    {
    }

    public static ASingletonClass Instance { get; private set; }
}

此外,将 SyncRoot 调用替换为 class 中设置为 new object() 的新对象字段。 ConcurrentDictionary 等较新的集合不支持 SyncRoot。

总的来说,设计看起来有缺陷。甚至可以使用像 ConcurrentDictionary 或 MemoryCache 这样的标准组件。

但是,一种可能的修补程序是再次检查锁内部缓存中的值。这应该会减少执行刷新操作的次数。

public bool TryGet(TKey key, out TValue value)
{
    if (_internalCache.TryGetValue(key, out value))
    {
        return true;
    }
    lock (_internalCache.SyncRoot)
    {
        // cache has already been refreshed
        if (_internalCache.TryGetValue(key, out value))
        {
           return true;
        }

        // refresh cache
        this._refreshCacheAction(this._internalCache);
        return _internalCache.TryGetValue(key, out value);
    }
}