我有一个主线程,用于填充一个List<T>
。然后我创建了一系列对象在不同的线程上执行,需要访问该列表。原始列表生成后将不会对其进行写入。我的想法是将该列表作为IEnumerable<T>
传递给在其他线程上执行的对象,主要是为了防止那些实现这些对象的人错误地向列表中写入数据。换句话说,如果保证原始列表不被写入,那么多个线程在IEnumerable上使用.Where
或foreach
是否安全?
我不确定迭代器本身是否在原始集合未更改的情况下是线程安全的。
我有一个主线程,用于填充一个List<T>
。然后我创建了一系列对象在不同的线程上执行,需要访问该列表。原始列表生成后将不会对其进行写入。我的想法是将该列表作为IEnumerable<T>
传递给在其他线程上执行的对象,主要是为了防止那些实现这些对象的人错误地向列表中写入数据。换句话说,如果保证原始列表不被写入,那么多个线程在IEnumerable上使用.Where
或foreach
是否安全?
我不确定迭代器本身是否在原始集合未更改的情况下是线程安全的。
IEnumerable<T>
不能被修改。 那么它有哪些非线程安全的地方呢?(如果您不修改实际的List<T>
)。
对于非线程安全,您需要写入和读取操作。
“迭代器本身”为每个foreach
实例化。
编辑:我简化了我的答案,但@Eric Lippert添加了有价值的评论。IEnumerable<T>
并没有定义修改方法,但这并不意味着访问操作是线程安全的(GetEnumerator
,MoveNext
等)。最简单的例子:将GetEnumerator
实现为此:
IEnumerator
实例更复杂的示例是缓存。
这是一个有趣的观点,但幸运的是我不知道任何标准类具有不线程安全的IEnumerable
实现。
每个调用 Where 或 foreach 的线程都有自己的枚举器 - 它们不会为同一列表共享一个枚举器对象。因此,由于 List 没有被修改,每个线程都使用自己的枚举器副本,所以不应该出现线程安全问题。
你可以在一个线程中看到这个过程 - 只需创建一个包含 10 个对象的 List,并从该 List 获取两个枚举器。使用一个枚举器枚举前 5 个项,并使用另一个枚举器枚举后 5 个项。你会发现两个枚举器都只枚举了前 5 个项,并且第二个枚举器没有从第一个枚举器离开的地方开始。
GetEnumerator
方法。不能保证返回一个单独的实例。 - Rufus LGetEnumerator()
。然而,List
确实是以这种方式实现的,如果有一个类以任何不同的方式实现它,那将会非常奇怪。事实上,我认为这将是一个bug。 - PhilList
不会被修改,那么从多个线程中读取它是安全的。这还包括使用它所提供的IEnumerator
实例。因此,如果您不再触碰原始列表并停止引用它,就可以确保多个线程可以无忧地读取它(只要您不尝试通过奇怪的方式再次修改它)。只读集合可以同时支持多个读取器,只要不修改集合即可。
ReadOnlyCollection
显式实现了 Add
方法。因此 myList.Add
是无法使用的,只有当 ((IList<T>)myList).Add
才能使用。对我来说,这已经足够安全了。 - CodesInChaos
Iterator i = list.iterator()
,然后使用该迭代器 - 在这种情况下,您需要为每个线程创建一个新的迭代器。 - Joseph Earl