我一直在构建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。如果是这样的话,请你告诉我是否需要使用它以及我将如何这样做。
由于
答案 0 :(得分:6)
是的,这里有潜在的数据竞争:
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<T>()
,因此可以选择:
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];
}
}
答案 1 :(得分:2)
是的,有一场比赛,因为如果没有找到钥匙:
if (!_settingsDictionary.ContainsKey(key))
然后到我们跑的时候:
_settingsDictionary[key] = _settingsService.GetSettings<T>();
可能有一把钥匙。
它甚至比不必要地更换更糟糕。如果添加密钥的线程1需要调整大小,那么当线程2添加并且它被认为需要调整大小时它可能是部分通过,并且几乎所有的注意都被关闭以进一步使用该字典。
重要的问题是,“这是一个案例,我们会有很多线程同时击中字典,或者这种情况很少见,但我们需要防范它吗?”
在第一种情况下,使用ConcurrentDictionary。在第二种情况下,只需为当前代码添加锁定即可。 ConcurrentDictionary在并发性方面提供了更好的性能(正如人们对名称所期望的那样),但是当通常只有一个线程实际命中它时,对普通字典的锁定会更好,但偶尔可以进行并发调用。
两者的替代方案,如果可能的设置数量很少,只需在开头加载该批次。如果没有更多的写作,字典对于多个读者是安全的,并且在零锁定的情况下,它是最快的。