【问题标题】:Parallel.Foreach loop gets different result than For loop?Parallel.Foreach 循环得到的结果与 For 循环不同?
【发布时间】:2016-04-27 08:23:07
【问题描述】:

我已经 编写简单的 for 循环遍历数组和 Parallel.ForEach 循环做同样的事情。但是,我得到的结果是不同的,所以我想问到底是怎么回事? :D

  class Program
  {
    static void Main(string[] args)
    {
      long creating = 0;
      long reading = 0;
      long readingParallel = 0;
      for (int j = 0; j < 10; j++)
      {
        Stopwatch timer1 = new Stopwatch();
        Random rnd = new Random();
        int[] array = new int[100000000];

        timer1.Start();
        for (int i = 0; i < 100000000; i++)
        {
          array[i] = rnd.Next(5);
        }
        timer1.Stop();

        long result = 0;
        Stopwatch timer2 = new Stopwatch();

        timer2.Start();
        for (int i = 0; i < 100000000; i++)
        {
          result += array[i];
        }
        timer2.Stop();

        Stopwatch timer3 = new Stopwatch();
        long result2 = 0;
        timer3.Start();
        Parallel.ForEach(array, (item) =>
          {
            result2 += item;
          });

        if (result != result2)
        {
          Console.WriteLine(result + " - " + result2);
        }

        timer3.Stop();

        creating += timer1.ElapsedMilliseconds;
        reading += timer2.ElapsedMilliseconds;
        readingParallel += timer3.ElapsedMilliseconds;
      }



      Console.WriteLine("Create : \t" + creating / 100);
      Console.WriteLine("Read: \t\t" + reading / 100);
      Console.WriteLine("ReadP: \t\t" + readingParallel / 100);

      Console.ReadKey();
    }
  }

所以在我得到结果的条件下: 结果 = 200009295; 结果2 = 35163054;

有什么问题吗?

【问题讨论】:

    标签: c# for-loop parallel.foreach


    【解决方案1】:

    += 运算符是非原子的,实际上执行多个操作:

    • 将result 指向的位置的值加载到内存中
    • 将array[i] 添加到内存值(我在这里简化)
    • 将结果写回result

    由于许多这些加法操作将并行运行,这不仅是可能的,而且其中一些操作之间可能会出现竞争,其中一个线程读取result 值并执行加法,但在它之前有机会将其写回,另一个线程获取旧的result 值(尚未更新)并执行加法。然后两个线程将各自的值写入result。无论哪一方赢得比赛,最终的结果都比预期的要少。

    这就是Interlocked class 存在的原因。

    您的代码很容易被修复:

    Parallel.ForEach(array, (item) =>
    {
        Interlocked.Add(ref result2, item);
    });
    

    如果Parallel.ForEach 在这种情况下最终比完全同步版本慢,请不要感到惊讶。这是因为

    • 您传递给Parallel.ForEach 的委托中的工作量非常很小
    • Interlocked 方法会产生轻微但不可忽略的开销,在这种特殊情况下会非常明显

    【讨论】:

    • 感谢您的广泛解释。你当然是对的!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-12
    • 2023-01-11
    • 1970-01-01
    • 2018-05-01
    • 2014-12-05
    相关资源
    最近更新 更多