List<T>.AddRange() 方法是否线程安全?

17

我可以在没有锁定的情况下,安全地从多个线程中调用List.AddRange(r)吗?如果不能,我会遇到什么问题?


1
顺便说一句,关于“什么样的问题?”- 当多个线程冲突时,您可能会随机时间收到异常。 - Steve Townsend
5个回答

19

不,该类的文档没有说明它是线程安全的,因此它不是线程安全的。

该类型的公共静态成员(在 Visual Basic 中为 Shared)是线程安全的。 任何实例成员都不能保证是线程安全的。

至于可能出现什么问题,请考虑 AddRange(newItems) 所执行的操作:

  • 检查内部数组中是否有足够的空间
  • 如果没有:
    • 分配一个新数组
    • 将当前项复制到新数组中
    • 设置一个字段指向新数组
  • 将 newItems 复制到内部数组中的正确位置
  • 更新“count”字段(用于控制下一个项插入的位置)

现在想象一下,如果将上述操作与另一个 AddRange() 的调用或者甚至只是读取一项的调用混合在一起会发生什么。


6

不是的,但我想补充一点,使用锁内执行 myList.AddRange(...); 要比多次使用锁来添加元素 lock (syncLock) { myList.Add(...) }; 更高效。

你会遇到什么问题?当一个线程正在添加一个项而另一个线程正在枚举 list 时,List<T> 会抛出一个特定的异常,因为它进行了一些内部版本控制,以防止我们这些可怜的开发人员遭遇麻烦的副作用。

此外,List<T> 内部保持一个数组,其中存储其项目。也许在数组中设置项目是相当原子的,但当达到该数组的容量时,将创建一个新数组,项目将从旧数组复制过来。因此,当一个线程想要添加东西时,如果拷贝正在进行,你可以想象事情会变得不同步。


3
在 .NET Framework 4.0 之前,没有任何 .NET 集合是线程安全的。因此,在您的代码中访问它之前,您需要锁定它 Collections and Synchronization (Thread Safety)
另一方面,.NET Framework 4.0 引入了新的 System.Collections.Concurrent 命名空间,其中包括细粒度的 Thread-Safe Collections
最后,如果您可以使用 .NET Framework 4.0,我强烈建议您这样做以满足您的需求,否则,请确保每次修改或访问集合时都要锁定它。
此外,静态集合应该是线程安全的,但请注意,成员不能保证是线程安全的。
编辑 #1
经过 Steve Townsend 的评论后进一步验证,我承认从版本 3.0 开始,.NET Framework 中有三个线程安全的集合。
  1. 同步集合泛型类;
  2. 同步键控集合泛型类;
  3. 同步只读集合泛型类.

非常抱歉,我也刚刚了解到它们的存在。=)


不是真的,System.Collections.Generic 中有一些在这里起作用并且早于4.0版本。 - Steve Townsend
@Steve Townsend:经过核实,你是对的,我是错的。此外,尽管我没有考虑到SynchronizedCollection<T>,但IList<T>泛型集合并不是线程安全的。谢谢你告诉我这个信息! - Will Marcouiller
1
没问题。你提到4.0集合,我要给你点赞。 - Steve Townsend

3
根据您的使用情况,SynchronizedCollection 可能适合您。但是,您将无法使用单次 AddRange。如果您仅使用此方法来填充集合,则可以使用 IEnumerable 构造函数重载来完成。

感谢您的点赞。我想让您知道,我已经编辑了我的答案以反映您提供给我的(我们所有人)这些新信息。 - Will Marcouiller

1

不,它不是线程安全的。

线程A可能会在您的列表上调用AddRange。它可能部分地遍历集合并切换线程。

线程B可能会在线程A完成之前调用Add/Remove等操作。


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