【问题标题】:Potential Concurrency Issue?潜在的并发问题?
【发布时间】:2012-08-29 10:48:14
【问题描述】:

我一直在构建 ASP.NET MVC 应用程序,当我启动它时我担心潜在的多线程问题。一个特别值得关注的是以下代码:

private static IDictionary<string, ISettings> _settingsDictionary = new Dictionary<string, ISettings>();

public T Settings<T>() where T : ISettings, new() {
    var key = typeof(T).FullName;

    if (!_settingsDictionary.ContainsKey(key))
        _settingsDictionary[key] = _settingsService.GetSettings<T>();

    return (T)_settingsDictionary[key];
}

注意字典被定义为静态的。这允许我缓存字典,以便它为应用程序长度的每个请求返回相同的实例。

这在本地测试时效果很好,但我担心数百名用户使用它时可能会受到影响。这导致我调查 ConcurrencyDictionary。请您告诉我是否需要使用它以及如果是这种情况我将如何使用它。

谢谢

【问题讨论】:

    标签: c# asp.net-mvc multithreading concurrency


    【解决方案1】:

    是的,这里存在潜在的数据竞争:

    if (!_settingsDictionary.ContainsKey(key))
        _settingsDictionary[key] = _settingsService.GetSettings<T>();
    

    这可能导致两个线程添加相同的键,因为它们可以随时被中断。

    您可以改用ConcurrentDictionary.GetOrAdd

    private static ConcurrentDictionary<string, ISettings> _settingsDictionary = new ConcurrentDictionary<string, ISettings>();
    
    public T Settings<T>() where T : ISettings, new() {
        var key = typeof(T).FullName;
    
        return _settingsDictionary.GetOrAdd(key, _settingsService.GetSettings<T>());
    }
    

    编辑:由于您不希望每次都执行_settingsService.GetSettings&lt;T&gt;(),因此可以选择:

    private static IDictionary<string, ISettings> _settingsDictionary = new Dictionary<string, ISettings>();
    private static object locker = new object();
    
    public T Settings<T>() where T : ISettings, new() {
        var key = typeof(T).FullName;
        lock(locker) 
        {
            if (!_settingsDictionary.ContainsKey(key))
                _settingsDictionary[key] = _settingsService.GetSettings<T>();
    
            return (T)_settingsDictionary[key];
        }
    }
    

    【讨论】:

    • 感谢您的回答。我刚刚尝试过并通过在 GetSettings 方法中放置一个断点进行了一些调试。但是我发现即使字典中存在一个项目,它也总是会命中断点。
    • @nfplee:因为调用GetOrAdd之前需要先对参数求值,所以会调用GetSettings,但是如果key存在,对结果不做任何操作。
    • 这有什么比说:return _settingsService.GetSettings();更好?
    • @nfplee:那你是如何将值添加到字典中的?
    • 我的意思是与我的原始示例(在问题中)相比。如果字典中不存在某个项目,则问题中的示例仅点击 GetSettings。 GetSettings 命中了我希望避免重复的数据库。
    【解决方案2】:

    是的,有一场比赛,因为如果在以下位置找不到钥匙:

    if (!_settingsDictionary.ContainsKey(key))
    

    那么到我们跑的时候:

    _settingsDictionary[key] = _settingsService.GetSettings<T>();
    

    可能有一把钥匙。

    这比只是不必要地更换更糟糕。如果线程 1 添加一个键需要调整大小,那么当线程 2 添加时它可能是中途,它被认为需要调整大小,并且几乎所有的赌注都没有进一步使用该字典。

    重要的问题是,“这是我们将有大量线程同时访问字典的情况,还是这种情况很少见但我们需要防范的情况?”

    在第一种情况下,使用 ConcurrentDictionary。在第二种情况下,只需为您当前的代码添加一个锁。 ConcurrentDictionary 在并发方面提供了更好的性能(正如人们从名称中所期望的那样),但是当通常只有一个线程实际命中它时,围绕普通字典的锁会更好,但偶尔的并发调用是可能的。

    替代两者,如果可能的设置数量很少,只需在开始时加载批次。如果没有更多的写作,字典对多个阅读者来说是安全的,而且零锁定是最快的。

    【讨论】:

    • 好点,但我没有得到的是,如果访问很少,如果你使用ConcurrentDictionary 或锁定它有什么不同,因为在任何情况下开销都可以忽略不计?
    • @Tudor,如果一个线程执行一堆重复调用很重要,但很少有几个线程处于该位置。不过,我现在要补充一点,随着项目的发展,它更常见的是最终并发性更高而不是更少,除非您积极努力减少组件的并发压力,因此如果有疑问,请使用 ConcurrentDictionary。
    • 您好,感谢您的建议。我已经接受了都铎王朝的回答,因为它提供了一个例子。
    • NP。 Tudor 是最好的,我只是添加一些额外的信息。
    猜你喜欢
    • 2010-10-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-05
    • 2010-11-18
    相关资源
    最近更新 更多