ConcurrentDictionary TryAdd与Item setter性能
我们在一个高流量的网站上,我们想要使用ConcurrentDictionary进行一些非常简单的缓存计算,以防止它为每个请求完成。 可能的输入数量足够有限,计算相对较重(拆分和重新连接字符串)。
我正在考虑代码
string result; if (!MyConCurrentDictionary.TryGetValue(someKey, out result)) { result = DoCalculation(someKey); // alternative 1: use Item property MyConcurrentDictionary[someKey] = result; //alternative 2: use TryAdd MyConcurrentDictionary.TryAdd(someKey, result); }
我的问题是:从绩效角度来看,哪种替代方案是最佳选择?
正如@SLaks所示,您的代码存在竞争条件。 构建ConcurrentDictionary
是为了通过提供以primefaces方式执行复杂操作的方法(例如GetOrAdd
和AddOrUpdate
来防止此类情况。
这是一个描述代码如何破坏的序列:
- 线程1执行
TryGetValue
,返回false - 线程2也是如此
- 两个线程都进行计算
- 线程2将值添加到字典并返回结果
- 线程1也是
- (使用索引器设置器)添加另一个值,覆盖前一个值并返回它
- (使用
TryAdd
)不会添加它,并且该方法返回的结果可能与字典中的结果不同
- 线程2现在的结果可能与字典中的结果不同
因此,您可以看到如何不使用GetOrAdd
使事情变得更复杂,并可能产生潜在的灾难性后果。 在你的情况下,如果你确定性地从键中计算一个字符串,那么这可能无关紧要 – 但实际上没有理由不使用这种方法。 它还简化了代码,并且可以(略微)提高性能,因为它只计算一次哈希码。
另一个结论是在索引器和TryAdd
之间进行选择更多的是正确性问题而不是性能问题。
也就是说,indexer []
和TryAdd
方法的性能是相同的,因为两者共享相同的实现( TryAddInternal
),它通过获取属于密钥桶的锁,然后检查密钥是否存在于字典中,以及是否更新字典。
最后,这是一个示例 ,说明如何正确构建像GetOrAdd
这样的方法。
你的代码完全坏了; 你假设这两行之间什么都不会改变。
你需要使用.GetOrAdd()
。
通常,在处理可变并发对象时,必须永远不要对该对象进行多次调用,因为它的状态可以随时改变。