考虑以下代码:
void DoSomething(int key)
{
concurrentDictionary.GetOrAdd(key, (k)=>
{
//Do some expensive over network and database to retrieve value.
});
考虑2个线程正在调用DoSomething(2)
。同时他们会看到字典中没有Key==2
的项目。考虑Thread1
开始使用昂贵的算法来检索值2。
问题1: Thread2
会等Thread1
完成工作吗?或者只是尝试检索值本身,并在将其添加到字典时将其丢弃? (因为Thread1
已添加此内容)
问题2:如果Thread2
没有等待,那么避免多次运行这种昂贵算法的最佳解决方案是什么?
答案 0 :(得分:6)
它在documentation中告诉你:
如果您在不同的线程上同时调用
GetOrAdd
,则可能会多次调用 addValueFactory ,但每次调用时可能不会将其键/值对添加到字典中。
对于问题2,我考虑从ConcurrentDictionary<int,Something>
更改为ConcurrentDictionary<int,Lazy<Something>>
,其中addValueFactory
方法只构建指定{{3}的Lazy<Something>
模式。然后,valueFactory
Lazy<Something>
将进行昂贵的操作
答案 1 :(得分:2)
考虑2个线程正在调用DoSomething(2)。 同时他们 会看到字典中没有Key == 2的项目。
我不认为粗体的情景是可能的,这将是一场竞争条件。 check-for-a-key操作受 GetOrAdd
内的锁定机制保护。无论哪个线程首先获得锁定,都有机会添加键/值。另一个线程将等待阻塞,然后只有GetOrAdd
释放第一个线程获取的锁,才会获得该值。
我错了。 answer by @Damien_The_Unbeliever是正确的。以下是GetOrAdd
实现的样子。锁只发生在TryGetValue
和TryAddInternal
内。我很想知道这种设计决策背后的背景。
public TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory)
{
TValue local;
if (key == null)
{
throw new ArgumentNullException("key");
}
if (valueFactory == null)
{
throw new ArgumentNullException("valueFactory");
}
if (!this.TryGetValue(key, out local))
{
this.TryAddInternal(key, valueFactory(key), false, true, out local);
}
return local;
}