Parallel.ForEach的异常行为

11

可能是重复问题:
C# 并行处理时的值存储

今天我在我的控制台应用程序中运行了一些性能测试,结果发现了一些非常出乎意料的事情。我的代码:

int iterations = 1000000;

var mainList = new List<string>();

for (int i = 0; i < iterations; i++)
{
    mainList.Add(i.ToString());
}

var listA = new List<string>();

Parallel.ForEach(mainList, (listItem) =>
                           {
                               if (Int32.Parse(listItem)%2 == 0)
                               {
                                   listA.Add(listItem);
                               }
                           });

Console.WriteLine("Parallel Count: {0}", listA.Count);

var listB = new List<string>();
foreach (var listItem in mainList)
{
    if (Int32.Parse(listItem) % 2 == 0)
    {
        listB.Add(listItem);
    }
}

Console.WriteLine("Sequential Count: {0}", listB.Count);

导致输出结果为:

并行计数:495939

顺序计数:500000

我运行了几次,发现并行循环似乎从未以正确的次数执行。有人能解释这种“不良行为”吗?并行循环可信吗?

P.S. 我知道提供的代码示例中有很多无意义的操作,比如将整数调用ToString()再进行解析,但这只是我在测试时随机编写的代码。提前感谢。


10
List<T> 不是线程安全的 - 当你从多个线程添加元素到一个不是线程安全的集合中时,就不能保证它能正确地工作。 - Lee
淘气的Parallel.ForEach,总是调皮捣蛋。 - Th4t Guy
1个回答

15
你的问题不在于Parallel.ForEach,而是List<int> - 这个类不是线程安全的。我猜测你正在遇到列表对象的线程安全问题。尝试使用ConcurrentBag<int>替代,问题可能会消失。
从Microsoft关于List<T>的线程安全性的说明:

为了允许集合被多个线程同时读写,你必须实现自己的同步。


1
请不要推荐ConcurrentBag,因为它在很多方面存在缺陷(https://dev59.com/2Ggv5IYBdhLWcg3wF89P)。您需要在线程安全和内存泄漏或更高的内存消耗之间进行权衡。一个简单的锁和List<T>也足够了。 - Alois Kraus
1
@AloisKraus,那是另一个相对简单的选项;我只是试图提供使示例正常工作的最简单方法。 - theMayer

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