枚举时C#替换集合

时间:2016-12-30 15:33:25

标签: c# multithreading

根据讨论here,在互联网上的某个地方,验证了在枚举它们时替换某些类型的集合是可能的/线程安全的。

我的测试似乎证实了这一点。

// This test confirmed insufficient by comments
var a = new List<int> { 1, 2, 3 };

Parallel.For(1, 10000, i => {
    foreach (var x in a)
        Console.WriteLine(i + x);
});
Parallel.For(1, 10000, i => a = new List<int> { 1, 2, 3, 4 });

在我开始在我的代码中实施之前,我非常希望阅读一些官方文档或有关此事实的具体参考。

有人可以验证/发布链接吗?

2 个答案:

答案 0 :(得分:4)

正如已经提到的那样,当你迭代它时,你实际上并没有变异a。你正在迭代它,然后在你完成迭代之后,你正在改变a一堆,因为Parallel.For将阻塞,直到它完成所有迭代的执行。

但是,即使你 与这里的迭代并行地突变a,它实际上也是非常安全的。 foreach将在一开始就读取a 的值,获取对列表的引用,然后从那一点开始,它永远不会将再次查看a 。它将使用本地副本来处理从a获得的列表的引用,因此它不会知道或关心在该点之后对变量a进行了哪些更改。因此,如果您正在改变列表a指向的列表并同时迭代a,那么您不知道正在迭代的列表是否是之前a中的列表或在另一个线程发生更改之后,但是您知道正在迭代的列表必须是一个列表或另一个列表,而不是一些错误或两者的混合。

现在,如果您正在改变 a引用的列表而不是将变量a变为指向新引用那么那就是完全不同。 List不是设计为同时从多个线程访问的,因此会发生各种各样的坏事。如果您使用专门设计为从多个线程访问的集合,并且您以某种方式使用它,那么它可以正常运行。

答案 1 :(得分:0)

只是为了补充Servy的回答和评论中所说的内容,你所拥有的并不是一个在迭代时并行修改变量的例子。您的Parallel.For循环按顺序运行 - 即首先迭代列表10000次(可能并行),然后您将其替换为新列表10000次(再次,可能并行)。

// This doesn't modify or replace the collection at all, it just iterates over it a bunch of times
Parallel.For(1, 10000, i => {
    foreach (var x in a)
        Console.WriteLine(i + x);
});

// This happens AFTER the previous Parallel.For loop completes
// Thus, you're not actually iterating over the loop at this point, just replacing it a bunch of times
Parallel.For(1, 10000, i => a = new List<int> { 1, 2, 3, 4 });

请注意,我并行地说可能 - 简单地在Parallel.For循环中放置一些东西并不能保证框架实际上会使用多个线程来完成任务,而你无法预测&#34;提前&#34;它会使用多少个线程。重点是这个代码甚至不一定证明这些任务是在多个线程上运行的(或者如果它们正在运行多少线程)。

此测试中的另一个缺陷:您每次都使用相同的集合替换类,因此在循环完成后您无法确定哪个线程进行了最终更新。让我们说它使用3个不同的线程来执行它 - A,B和C.你怎么知道哪一个对集合进行了最后一次更新?回想一下,Parallel.For循环保证按顺序执行,因此它可以由三者中的任何一个更新。来自documentation(强调我的):

  

并行循环的语法与for和foreach非常相似   您已经知道的循环,但并行循环在a上运行得更快   有可用核心的计算机。 另一个不同之处在于,与之不同   顺序循环,没有为并行定义执行顺序   循环。步骤通常同时并行进行。的有时候,   两个步骤以与循环相反的顺序发生   是连续的。唯一的保证就是所有的循环   迭代将在循环结束时运行。

基本上,然后,使用Parallel.For循环,你不知道&#34;提前&#34;并行度,是否它完全使用并行性,甚至步骤将执行的顺序(因此使用这个结构必然会放弃对代码实际执行方式的相当大的控制)。