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

36
ConcurrentDictionary的文档没有明确说明,因此我认为我们不能指望委托valueFactoryupdateValueFactory从 GetOrAdd() 和 AddOrUpdate() 操作中分别执行时会被同步。
因此,我认为如果需要并发控制的资源,我们不能在它们内部实现使用资源,除非手动实现自己的并发控制,可能只需对委托使用[MethodImpl(MethodImplOptions.Synchronized)]即可。
我是正确的吗?或者ConcurrentDictionary是线程安全的,我们可以期望对这些委托的调用自动同步(也是线程安全的)?
2个回答

36

是的,您说得对,用户委托不会被ConcurrentDictionary同步。如果需要同步这些内容,那就由您来负责。

MSDN本身也是这样说的:

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

请参见“如何:向ConcurrentDictionary添加和删除项”

这是因为ConcurrentDictionary无法知道您提供的委托将执行什么操作或其性能,因此,如果它试图在它们周围进行锁定,可能会严重影响性能,并破坏ConcurrentDictionary的价值。

因此,如果需要同步委托,则由用户负责。上面的MSDN链接实际上有一个很好的示例,说明了它所做和未做的保证。


3
所以我将 GetOrAdd 方法调用包装在一个锁中,但这样做使 ConcurrentDictionary 的目的失去意义。有更好的方法吗? - John
我有点困惑,我们的意思是说如果将委托作为键传递,那么在评估时它的执行不会被同步吗?或者如果我们有一个值类型为“委托”的字典...当我们尝试执行委托(即dictionary[key](argument);)时,执行不会被同步吗?还是我完全错了?谢谢。 - Snoop
@Snoopy:评估要添加的值的委托未同步。这意味着,如果您的委托执行的操作不是线程安全的并且您没有尝试自己同步该操作,则会发生糟糕的事情。但是,充当值工厂的委托通常不会执行本质上不是线程安全的操作。大多数情况下,委托可能只是执行new SomeObject(),这绝对是线程安全的,因为它是一个无状态操作。 - Brian Gideon
2
@John:虽然没有得到很好的解释,但仍然有价值的部分是,虽然委托可能被执行多次,但只有在键的值一旦生成一个新值后没有被改变,新值才会被插入,否则-如果值在委托运行时更改-它将再次尝试。 - David Burg

27

这些代理不仅未同步,而且甚至不能保证只发生一次。实际上,在每次对 AddOrUpdate 的调用中,它们可以被执行多次。

例如,AddOrUpdate 的算法大致如下。

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

需要注意以下两点。

  • 没有对委托的执行进行同步。
  • 由于在循环内部调用,因此可能会多次执行委托。

因此,您需要确保执行以下两个操作:

  • 为委托提供自己的同步。
  • 确保您的委托没有任何副作用依赖于它们被执行的次数。

6
我刚刚检查了一下,似乎GetOrAdd只会调用ValueFactory一次。因此,只有AddOrUpdate有可能多次调用委托函数。这可能是微软的一个错误...不确定。当回答一个类似的问题时,我发现了这个问题很久以前。(原文链接:https://dev59.com/ClDTa4cB1Zd3GeqPMdAh#3831892) - Brian Gideon
到目前为止,这种行为有没有发生任何变化? - Sebastian
@SebastianGodelet:我不确定。看起来更像是一个bug,所以这种行为可能最终会被“修复”。 - Brian Gideon
@Softlion:这很不幸。它肯定会减少AddOrUpdate的实用性。看起来可以对逻辑进行一些微小的增强,以防止那些委托执行多次,所以我有点失望,微软还没有改变逻辑。 - Brian Gideon
1
我理解微软的观点是“不要因此问题联系我们的支持”或“保持安全,在家工作”,这种想法推动了这种实现。这是一个0%风险观点。但由于“并发”形容词的暗示,我们现在都有了无法工作的代码!至少可以在文档中选择加入并发操作,并发出警告...我很幸运发现了一些奇怪的事情并挖掘它...否则,无法工作的代码将发布给许多客户。 - Softlion
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接