【问题标题】:Why does accessing and mutating a System.Collections.Generic.Dictionary in parallel not result in exceptions/crashes?为什么并行访问和改变 System.Collections.Generic.Dictionary 不会导致异常/崩溃?
【发布时间】:2021-04-07 23:59:04
【问题描述】:

有人可以解释或指向一些可以向我解释为什么此代码不会引发任何异常的文档:

var d = new Dictionary<object, object>();
System.Threading.Tasks.Parallel.ForEach(new Action[] {
    () => {
        System.Threading.Tasks.Parallel.For(0, 1024, i => {
            d.Add(new object(), new object());
        });
    },
    () => {
        System.Threading.Tasks.Parallel.For(0, 1024, i => {
            d.TryGetValue(new object(), out _);
        });
    },
}, a => a());

我尝试在我的 8 核计算机上运行它,期望某些东西会引发有关无效操作或空引用等的异常,但它从来没有。虽然我最终得到了一个包含错误数量元素的字典,但它如何不会抛出异常和/或崩溃?

【问题讨论】:

  • 根据文档Add,如果密钥已经在字典中,则不会发生这种情况,因为您每次都创建一个新对象。而TryGetValue 设计为不扔。那么您究竟会期待哪些例外情况?
  • @juharr 预计两个线程同时发生重新平衡会造成一些破坏......但由于 OP 只进行 1000 次迭代,Parallel.ForEach 甚至不需要运行足够的线程来导致真正的麻烦很难可靠地触发。此外,总体上不正确的数据是不受保护的多线程访问更常见的结果,但 OP 根本不关心正确性。
  • 定义上的竞争条件意味着它可能不会在大多数运行中引起问题。您是否运行过 1000 万次测试?

标签: c# thread-safety clr race-condition


【解决方案1】:

仅仅因为某些东西不是线程安全的并不意味着从多个线程中使用它会导致它抛出异常。 Dictionary 类不是线程安全的,一次只能被一个线程使用。它可能不包括对同时访问它的线程的任何检查,因为这太昂贵了。它只是假设一次只有一个线程访问它。

【讨论】:

  • 我猜这一切都归结为 1. 读取和写入引用类型和整数等,是原子的和 2. 垃圾收集。不像 C 实现可能会尝试释放某些东西,而另一个线程可能正在使用它并导致在释放和/或访问未初始化的内存后使用,我猜 C# 没有这个问题。
【解决方案2】:

同时从多个线程中改变一个非线程安全的组件既不会保证异常也不会导致崩溃。实际上它绝对不能保证什么。组件的行为正式成为“未定义”,这意味着任何事情都可能发生,包括根本没有发生任何不好的事情。但是如果发生了不愉快的事情,那么你就不能去找供应商并填写错误报告,要求修复错误。因为没有bug。供应商可以通过以下方式回复您的请求:

尊敬的客户,封条破损,保修无效。你选择进入未定义领域的大黑森林,那里没有任何规则适用,现在你是自己的。

【讨论】:

    猜你喜欢
    • 2011-10-19
    • 2021-06-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-03
    相关资源
    最近更新 更多