【问题标题】:Race conditions when adding to, but not reading from, a List<T> (or Stack or Queue) - what happens?添加但不读取 List<T>(或堆栈或队列)时的竞争条件 - 会发生什么?
【发布时间】:2011-04-20 10:11:18
【问题描述】:

注意:我问的是与 3.5 框架相关的问题,因此我没有在 4.0 中包含任何较新的多线程结构(我仍在学习)。

我一直试图对此提出一个答案,以试图结束我一直在争论的问题,但我觉得我没有找到关于会发生什么或可能发生什么的结论性描述在以下场景中。

假设您有一个应用程序具有多个线程,这些线程都在生成对象,每个线程都生成一个唯一的对象。集合(List、Stack 或 Queue)的单个实例是对象创建后的存储库,并且对象在添加到集合后实际上是不可变的。

在此过程中,对集合的唯一操作是添加项目。没有读取、删除或计数。集合中项目的顺序无关紧要。

问题是,如果在各个线程尝试添加对象时集合周围没有锁定,这真的有问题吗?

我说是的,但是几乎所有关于为什么竞争条件有问题的教科书描述都在一端读取,另一端进行竞争,或者写入单个变量,所以我没有足够的证据来证明有效地论证我的观点。

我之所以说“是”,基本上是因为集合并非设计为线程安全的,因此即使只执行“写入”,您也可能会遇到意外或未定义的行为。我猜有两个线程可能会尝试将它们的对象添加到集合中的同一个插槽的风险,因此当它被覆盖时你会丢失一个对象,但我没有找到任何说它是的确实有可能。或者可能存在一些幕后问题,例如当集合需要自行增长以容纳更多项目时。

有人可以向我提供一些信息来帮助平息这个论点吗?我特别想知道是否有任何事情可以证明我错了。

【问题讨论】:

  • “幕后问题,例如当集合需要自行增长以容纳更多项目时”将是我的第一个猜测。

标签: c# multithreading concurrency


【解决方案1】:

List&lt;T&gt;(或堆栈等)添加项目不是线程安全的

这涉及

  • 检查内部数组大小是否足够
  • 如果没有,创建一个新数组,复制所有项
  • 在数组中设置索引等于List&lt;T&gt;长度的项

以上进程都不是线程安全的,并且 .NET 框架代码中没有同步。因此,如果您不同步,我保证您的列表将被损坏。

【讨论】:

    【解决方案2】:

    将项目添加到列表中涉及到内存的读取和写入,因此除非存在同步,否则存在竞争。

    【讨论】:

      【解决方案3】:

      您基本上是对的,尽管一些更简单的结构(如堆栈或队列)可能“意外地”是线程安全的。您无法保证任何集合的“内部”是如何实现的。例如,列表由数组支持。如果您添加一个新项目导致它扩大数组,它会(可能)将所有值复制到一个新的更大的数组中。这在其实现中可能是线程安全的,但由于它不是线程安全对象,因此它的线程安全没有契约。

      【讨论】:

      • 将值复制到新数组怎么可能是线程安全的?!
      • 此外,假设内部实现细节在 .NET Framework 的各个版本中保持不变是在玩火。你应该永远假设某些东西是线程安全的,除非文档明确证明它是这样的。
      • @David 和@Cody。我不确定你们是否不同意我的观点。我的回答是 100% 同意你的说法。堆栈和队列可能是线程安全的,但如果是这样,那只是“偶然”。另一方面,列表显然不是。要点是:不要指望它,即使它在并发条件下似乎可以正常工作。
      • 当你说“这可能是线程安全的”时,正确的说法应该是“这肯定不是线程安全的”
      • @MichaelMeadows:如果一个从未使用过像InsertDelete这样的方法,需要在之后更改项目的代码,那么按索引寻址的列表不能是线程安全的,没有什么特别的原因通过int Add(T item) 方法添加它,该方法返回新创建项目的索引。如果T 是类类型并且列表项不允许为空,那么这样的类甚至可以是无锁的,而不会产生大量开销。
      猜你喜欢
      • 1970-01-01
      • 2019-07-04
      • 2017-12-29
      • 2017-03-31
      • 1970-01-01
      • 2021-03-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多