代码之家  ›  专栏  ›  技术社区  ›  Inisheer

parallel.for():更新循环外的变量

  •  35
  • Inisheer  · 技术社区  · 14 年前

    我只是在寻找新的.NET 4.0功能。有了这个,我尝试用 Parallel.For 正常 for(x;x;x) 循环。

    然而,大约50%的时间我得到了不同的结果。

    long sum = 0;
    
    Parallel.For(1, 10000, y =>
        {
            sum += y;
        }
    );
    
    Console.WriteLine(sum.ToString());
    
    sum = 0;
    
    for (int y = 1; y < 10000; y++)
    {
       sum += y;
    }
    Console.WriteLine(sum.ToString());
    

    我的猜测是线程试图同时更新“sum”。
    有没有明显的方法?

    7 回复  |  直到 8 年前
        1
  •  69
  •   Alex Bagnolini    14 年前

    你不能这样做。 sum 正在您的并行线程之间共享。你需要确保 总和 一次只能由一个线程访问变量:

    // DON'T DO THIS!
    Parallel.For(0, data.Count, i =>
    {
        Interlocked.Add(ref sum, data[i]);
    });
    

    但是…这是一个反模式,因为您已经有效地序列化了循环,因为每个线程都将锁定 Interlocked.Add .

    您需要做的是添加子总计,然后像这样在末尾合并它们:

    Parallel.For<int>(0, result.Count, () => 0, (i, loop, subtotal) =>
        {
            subtotal += result[i];
            return subtotal;
        },
        (x) => Interlocked.Add(ref sum, x)
    );
    

    您可以在msdn上找到关于这一点的进一步讨论: http://msdn.microsoft.com/en-us/library/dd460703.aspx

    插头: 您可以在第2章的 A Guide to Parallel Programming

    以下也绝对值得一读…

    Patterns for Parallel Programming: Understanding and Applying Parallel Patterns with the .NET Framework 4 - Stephen Toub

        2
  •  16
  •   BlueRaja - Danny Pflughoeft    14 年前

    sum += y; 实际上是 sum = sum + y; . 由于以下比赛条件,您得到的结果不正确:

    1. 线程读取 sum
    2. 线程读取 总和
    3. thread1计算 sum+y1 ,并将结果存储在 总和
    4. 螺纹2计算 sum+y2 ,并将结果存储在 总和

    总和 现在等于 求和+Y2 而不是 sum+y1+y2 .

        3
  •  5
  •   SLaks    14 年前

    你的猜测是正确的。

    当你写作时 sum += y ,运行时执行以下操作:

    1. 将字段读到堆栈上
    2. 添加 y 堆栈
    3. 将结果写回字段

    如果两个线程同时读取字段,则第一个线程所做的更改将被第二个线程覆盖。

    你需要使用 Interlocked.Add 将加法作为单个原子操作执行。

        4
  •  4
  •   Eric Mickelsen    14 年前

    增加long不是原子操作。

        5
  •  4
  •   Mgetz    14 年前

    我认为很重要的一点是要区分这个循环不能进行并行划分,因为正如上面所提到的,循环的每个迭代都依赖于先前的迭代。parallel for是为显式并行任务而设计的,例如像素缩放等,因为循环的每个迭代在其迭代之外都不能有数据依赖关系。

    Parallel.For(0, input.length, x =>
    {
        output[x] = input[x] * scalingFactor;
    });
    

    上面是一个代码示例,它允许对并行性进行简单的分区。然而,警告一句,并行性是要付出代价的,即使我上面作为例子使用的循环也太简单了,不必为它费心,因为设置时间比通过并行性节省的时间要长。

        6
  •  3
  •   Douglas    11 年前

    似乎没有人提到一个重要的点:对于数据并行操作(如操作),使用plinq而不是 Parallel 班级。OP的代码实际上很容易并行化:

    long sum = Enumerable.Range(1, 10000).AsParallel().Sum();
    

    上面的代码段使用 ParallelEnumerable.Sum 方法,尽管也可以使用 Aggregate 对于更一般的场景。参考 Parallel Loops 第章解释这些方法。

        7
  •  -1
  •   Chris Shain    8 年前

    如果此代码中有两个参数。 例如

    long sum1 = 0;
    long sum2 = 0;
    
    Parallel.For(1, 10000, y =>
        {
            sum1 += y;
            sum2=sum1*y;
        }
    );
    

    我们该怎么办?我猜必须使用阵列!