我们正在一个高流量的网站上,我们希望使用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);
}
我的问题是:从绩效角度来看,哪种选择是最佳选择?
答案 0 :(得分:8)
你的代码完全坏了;你假设这两行之间什么都不会改变。
您需要使用.GetOrAdd()
。
通常,在处理可变并发对象时,必须永远不要对该对象进行多次调用,因为它的状态可以随时改变。
答案 1 :(得分:2)
正如@SLaks所示,您的代码存在竞争条件。 ConcurrentDictionary
旨在通过提供执行原子等复杂操作的方法来防止此类情况,例如GetOrAdd
和AddOrUpdate
。
这是一个描述代码如何破解的序列:
TryGetValue
,返回false TryAdd
)没有添加它,并且该方法返回的结果可能与字典中的不同 因此,您可以看到如何不使用GetOrAdd
使事情变得更复杂,并可能产生潜在的灾难性后果。在您的情况下,如果您确定性地从密钥中计算字符串,那么它可能无关紧要 - 但实际上没有理由不使用此方法。它还简化了代码,并且可以(略微)提高性能,因为它只计算一次哈希码。
另一个结论是在索引器和TryAdd
之间进行选择更多的是正确性而不是性能。
也就是说,索引器[]
和TryAdd
方法的性能是相同的,因为它们共享相同的实现(TryAddInternal
),这通过获取属于key的存储桶,然后检查字典中是否存在密钥,以及是否更新字典。
最后,这里的an example展示了如何正确构建GetOrAdd
等方法。