【问题标题】:Parallel.For List<Int32>Parallel.For 列表<Int32>
【发布时间】:2012-03-07 18:45:01
【问题描述】:

我写了以下几行代码:

    private static List<Int32> GetRandomList_Serial()
    {
        List<Int32> returnValue = new List<int>();
        Random random = new Random();

        for (int i = 0; i < 10000000; i++)
        {
            returnValue.Add(random.Next());
        }
        returnValue.Sort();
        return returnValue;
    }

然后我写了这段代码:

    private static List<Int32> GetRandomList_Parallel()
    {
        List<Int32> returnValue = new List<int>();
        Random random = new Random();

        Parallel.For(0, 10000000, y =>
        {
            returnValue.Add(random.Next());
        });

        returnValue.Sort();
        return returnValue;

    }

串行工作正常,并行抛出此异常:

System.ArgumentException 未被用户代码处理

目标数组不够长。检查 destIndex 和长度,以及数组的下限。

有人知道为什么吗?

【问题讨论】:

  • 并行化这样一个微不足道的操作是没有意义的。此外,您无法真正并行化涉及共享资源(如列表)的操作。

标签: .net task-parallel-library


【解决方案1】:

您使用的 List 不是线程安全的。使用 ConcurrentBag。我在切换到并行循环时一直遇到这个问题。它会间歇性地发生,并非每次都发生,因此很难检测到。

【讨论】:

  • @SLaks 我同意,但这不是他的问题。他的问题是为什么他会看到该错误,它将为在处理需要它的任务的并行循环时遇到类似问题的其他人提供有价值的信息。
  • Random 也不是线程安全的,所以如果切换到ConcurrentBag&lt;&gt; 后仍然失败,请不要感到惊讶。
【解决方案2】:

是的,List&lt;&gt; 不是线程安全的。但是,您可以通过将 List 预分配到您想要的大小来并行化,然后使用 [](索引)来填充值。

换句话说,不要并行增长你的数组。一开始就种植,然后并行填充。

编辑:

更多信息。

List&lt;&gt; 是一个简单的数组,可以为了方便而增长。因此,如果您预先分配它(一开始就将它增加到 1000 的 LENGTH),它的大小 (.Count) 在填充过程中不会改变。

因此,如果 Parallel.For 使用任何类型的分区,它将为将处理循环的每个线程分配不同的索引。并且,数组的每个 ELEMENT 将被精确填充一次。

我认为这没有任何问题,除了Random.Next() 不是线程安全的并且可能返回乱码。但是,它应该这样做,对吧?

新编辑:

更多信息:

如果需要使用 List,则预分配意味着:

List<int> myList=new List<int>(MY_COUNT);
//  very unefficient method of pre-allocation, 
//  but there is no other way to assign .Count
for (int n=0; n<MY_COUNT; n++) {
    myList.Add(0);
}
//  proceed with Parallel.For

如果您可以将类型切换为其他类型,我会为此推荐一个数组:

int[] myList=new int[MY_COUNT];
//  proceed with Parallel.For

【讨论】:

  • 即使列表被初始化为正确的大小,您仍然可能遇到问题——只是问题中报告的具体问题不是。例如,两个线程可以将不同的值写入列表中的同一位置,这会导致第一个这样的值丢失。
  • 邹是说parallel.for在两个不同的线程中使用相同的索引?嗯……
  • Daniel,两个线程可能会使用相同的索引。在第一个线程有机会增加它之前,第二个线程可以读取列表的长度。然后,两个线程将写入列表底层数组中的相同索引。
  • @phoog 不,它不会,因为我的建议(见我的回答)是预先分配列表。所以列表大小(长度)在 Parallel.For 期间总是相同的。
  • Daniel,列表的容量与其元素数不同。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-04
  • 2020-11-21
  • 2011-02-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多