ReadOnlyCollection<T>线程安全

问题描述 投票:0回答:3

ReadOnlyCollection(of T) 的文档指出:

A ReadOnlyCollection(Of T)
可以支持多个读者并发,只要集合不被修改。即便如此,通过集合进行枚举本质上并不是线程安全的过程。为了保证枚举过程中的线程安全,可以在整个枚举过程中锁定集合。为了允许多个线程访问集合进行读写,您必须实现自己的同步。

我的问题是关于粗体部分的:

  1. 为什么通过集合进行枚举本质上不是线程安全的
  2. 可能的影响是什么,以及
  3. 常用的解决方法是什么?
.net multithreading concurrency thread-safety immutability
3个回答
11
投票

C# 有 一个相当不错的 一个还不错的集合模型,但是

ReadOnlyCollection
类是整个模型中构思(或命名)最不幸的类之一。它应该更合适地称为只读列表,而不是只读集合。

现在,回答你的问题,它只是在构建时提供的

IList
的只读包装。 (这就是为什么它被称为“只读”而不是“不可变”。)构造
ReadOnlyCollection
的代码可能会保留对原始可变列表的引用,并且可能会修改该列表,从而产生所有后果用于多线程访问。

如果集合是不可变的,则枚举集合将是线程安全的;但由于它只是可能可变列表的只读包装,因此它只能与可变列表一样线程安全,这是根本不是线程安全。

通过可变集合进行枚举本质上不是线程安全的,原因有两个:

  1. 同样的原因,即使在单线程场景中,您也无法在枚举集合时修改集合。 (如果您尝试这样做,您将在 Java 中获得

    ConcurrentModificationException
    ,在 C# 中获得
    InvalidOperationException
    。)这是因为枚举器维护某种状态(例如,沿内部数组的索引),但此状态可能会被渲染由于原始集合的突变而无效。 (例如,缩短数组可能会使该索引指向数组末尾。)如果只有一个线程,则可以确保在枚举它时不会改变它,但在多线程场景中一个线程可能正在枚举集合,而另一个线程可能正在更改它,而您对此无法控制。

  2. 这与任何可变的(和非原子的)本质上不是线程安全的原因相同。 (集合是一件复杂的事情,所以它肯定不是原子的,而普通整数是原子的。)当一个线程正在读取数据而另一个线程正在写入相同的数据时,数据对于读取线程来说似乎是损坏的。最简单的示例是在 32 位架构上修改 64 位整数,其中此类整数是非原子的。在写入该整数的前半部分之后但在写入该整数的后半部分之前,修改 64 位整数的线程可能会被另一个线程抢占。抢占线程将读取一个写了一半的整数,这将是垃圾。当我们谈论整个数组而不仅仅是整数时,这种效果更加明显。

至于您要求的解决方法,您可以使用锁定,也可以遵循无锁(或尽可能少锁)原则并处理原始可变列表的真实副本而不是它周围的包装纸。


3
投票

这篇 MSDN 文章 指出:“ReadOnlyCollection 泛型类的实例始终是只读的。只读集合只是一个带有防止修改集合的包装器的集合”

所以我相信迭代不是线程安全的,因为它内部使用普通的非线程安全对象集合。

这意味着,如果集合以某种方式发生变化,不同的线程可能会获得不同的值。使用

lock

 语句可以避免不同线程同时并发访问集合。


0
投票
问题1和2已在其他帖子中得到解答。

对于 3 个,请使用

System.Collection.Immutable.ImmutableList


    

© www.soinside.com 2019 - 2024. All rights reserved.