ConcurrentDictionary陷阱 - 来自GetOrAdd和AddOrUpdate的委托工厂是否同步?

Luc*_*ano 33 .net c# concurrency multithreading thread-safety

文档ConcurrentDictionary没有明确的状态,所以我想我们不能指望委托valueFactoryupdateValueFactory让它们的执行同步(分别来自GetOrAdd()和AddOrUpdate()操作).

因此,我认为我们无法在其中实现需要并发控制的资源,而无需手动实现我们自己的并发控制,可能只是使用[MethodImpl(MethodImplOptions.Synchronized)]代理.

我对吗?或者ConcurrentDictionary是线程安全的事实,我们可以预期对这些代理的调用会自动同步(线程安全)?

Jam*_*are 34

是的,你是对的,用户代表不同步ConcurrentDictionary.如果您需要那些同步,这是您的责任.

MSDN本身说:

此外,尽管ConcurrentDictionary的所有方法都是线程安全的,但并非所有方法都是原子方法,特别是GetOrAdd和AddOrUpdate.传递给这些方法的用户委托是在字典的内部锁之外调用的.(这样做是为了防止未知代码阻塞所有线程.)

请参阅"如何:从ConcurrentDictionary添加和删除项目"

这是因为ConcurrentDictionary你不知道你提供的委托会做什么或它的性能,所以如果它试图锁定它们,它可能会对性能产生负面影响并破坏ConcurrentDictionary的值.

因此,如果有必要,用户有责任同步其代理.上面的MSDN链接实际上有一个很好的例子,它保证它做和不做.

  • 所以我将GetOrAdd方法调用包装在一个锁中,但这使得ConcurrentDictionary的目的无用.有最好的方法吗? (2认同)
  • @John:没有很好解释的部分,但使它仍然有价值的部分是,虽然委托可能会被多次执行,但只有在委托生成新值后键的值没有更改时才会更改插入新值,否则 - 如果在委托运行时更改了值 - 它将重试。 (2认同)

Bri*_*eon 25

这些代表不仅没有同步,而且甚至不能保证只发生一次.事实上,它们每次调用都可以执行多次AddOrUpdate.

例如,该算法AddOrUpdate看起来像这样.

TValue value;
do
{
  if (!TryGetValue(...))
  {
    value = addValueFactory(key);
    if (!TryAddInternal(...))
    {
      continue;
    }
    return value;
  }
  value = updateValueFactory(key);
} 
while (!TryUpdate(...))
return value;
Run Code Online (Sandbox Code Playgroud)

请注意这里的两件事.

  • 没有努力同步代表的执行.
  • 代理可能会被多次执行,因为它们是循环调用的.

所以你需要确保你做两件事.

  • 为代理提供您自己的同步.
  • 确保您的代表没有任何副作用,这些副作用取决于它们的执行次数.

  • @Luciano:我刚刚检查过,并且`GetOrAdd`似乎一次调用`ValueFactory`.所以只有`AddOrUpdate`才有可能多次调用代理.这可能是微软的一个错误......不确定.我在[回答类似问题]时发现了很长时间(http://stackoverflow.com/a/3831892/158779). (5认同)