首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >锁定成本

锁定成本
EN

Stack Overflow用户
提问于 2015-10-20 09:04:03
回答 1查看 202关注 0票数 3

我有一个缓存(由一个web应用程序使用),它在内部使用两个缓存--一个短期缓存(仅在请求中使用)和一个“永久”使用(跨请求)的长期缓存。

我有以下代码,请注意,所有底层数据结构都是线程安全的。

代码语言:javascript
复制
public TCache Get(CacheDependency cachdeDependancy, Func<CacheDependency, TCache> cacheItemCreatorFunc)
{
    TCache cacheItem;
    if (shortTermCache.TryGetValue(cachdeDependancy.Id, out cacheItem))
    {
        return cacheItem;
    }

    DateTime cacheDependancyLastModified;
    if (longTermCache.TryGetValue(cachdeDependancy.Id, out cacheItem)
        && IsValid(cachdeDependancy, cacheItem, out cacheDependancyLastModified))
    {
        cacheItem.CacheTime = cacheDependancyLastModified;
        shortTermCache[cachdeDependancy.Id] = cacheItem;
        return cacheItem;
    }

    cacheItem = cacheItemCreatorFunc(cachdeDependancy);

    longTermCache.Add(cachdeDependancy.Id, cacheItem);
    shortTermCache[cachdeDependancy.Id] = cacheItem;
    return cacheItem;
}

显然,当运行并发(即多个web请求)时,上面的代码仍然可能(甚至可能)是不一致的。然而,我写了一些单元测试,我看到的是,从来没有“异常”发生。可能发生的情况是,即使已经存在,也会再次添加相同的项。

尽管如此,我仍然认为,如果有一个解决方案总是正确和一致的,那就太好了。

因此,我使用简单的双重检查锁机制重写了这段代码(也许这样做更好,为另一个缓存添加另一个/秒锁?):

代码语言:javascript
复制
public TCache Get(CacheDependency cachdeDependancy, Func<CacheDependency, TCache> cacheItemCreatorFunc)
{
    TCache cacheItem;
    if (shortTermCache.TryGetValue(cachdeDependancy.Id, out cacheItem))
    {
        return cacheItem;
    }

    lock (_lockObj)
    {
        if (shortTermCache.TryGetValue(cachdeDependancy.Id, out cacheItem))
        {
            return cacheItem;
        }

        DateTime cacheDependancyLastModified;
        if (longTermCache.TryGetValue(cachdeDependancy.Id, out cacheItem)
            && IsValid(cachdeDependancy, cacheItem, out cacheDependancyLastModified))
        {
            cacheItem.CacheTime = cacheDependancyLastModified;
            shortTermCache[cachdeDependancy.Id] = cacheItem;
            return cacheItem;
        }

        cacheItem = cacheItemCreatorFunc(cachdeDependancy);

        longTermCache.Add(cachdeDependancy.Id, cacheItem);
        shortTermCache[cachdeDependancy.Id] = cacheItem;
        return cacheItem;
    }
}

我认为这段代码现在在多线程环境中正确工作。

然而,我不确定的是:这会不会非常慢,因此也会破坏缓存的用途?如果缓存有时会有“不一致”的行为,那么接受这个问题会更好吗?因为如果同时有1000个web请求,所有这些请求都必须等待,直到它们能够进入锁定区域。或者这根本不是一个问题,因为一个CPU一次只有特定数量的内核(因此是“真正的”并行线程),而这个性能损失总是很小的吗?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2015-10-20 09:36:59

如果您使用ConcurrentDictionary,您已经有了一种方法来做您想做的事情--您可以简单地使用GetOrAdd方法:

代码语言:javascript
复制
shortTermCache[cacheDependency.Id] = 
  longTermCache.GetOrAdd(cacheDependency.Id, _ => cacheItemCreatorFunc(cachdeDependancy));

(既快又容易:)

您甚至可以将其扩展为包括短期缓存检查:

代码语言:javascript
复制
return
  shortTermCache.GetOrAdd
  (
    cacheDependency.Id,
    _ =>
    {
      return longTermCache
             .GetOrAdd(cacheDependency.Id, __ => cacheItemCreatorFunc(cacheDependency));
    }
  );

虽然对每个请求缓存使用ConcurrentDictionary是不必要的,但它实际上并不一定是线程安全的。

至于你的原始代码,是的,它坏了。在测试过程中,您没有发现这一点并不令人惊讶--多线程问题通常很难重现。这就是为什么您希望代码是正确的,首先也是最重要的--这意味着您必须了解到底发生了什么,以及可能发生什么样的并发问题。在您的示例中,有两个共享引用:longTermCachecacheItem本身。即使您正在处理的所有对象都是线程安全的,您也无法保证您的代码也是线程安全的--在您的情况下,可能会有关于cacheItem (线程安全程度如何?)的争论,或者在此期间可能有人添加了相同的缓存项。

这种中断的确切程度在很大程度上取决于实际的实现--例如,如果一个Id相同的项已经存在,或者它可能没有出现,Add可能会抛出一个异常。您的代码可能希望所有缓存项都是相同的引用,也可能不会。cacheItemCreatorFunc可能有可怕的副作用,或者运行起来很昂贵,或者没有。

您添加的lock更新确实解决了这些问题。但是,它不能处理您在整个地方泄漏cacheItem的方式,例如。除非cacheItem也是完全线程安全的,否则您可能会遇到一些难以跟踪的bug。而且我们已经知道它也不是一成不变的--至少,您正在改变缓存时间。

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/33232384

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档