【问题标题】:Parallel.For does not wait all iterationsParallel.For 不等待所有迭代
【发布时间】:2015-03-14 23:55:54
【问题描述】:

我正在使用遗传算法构建优化程序。我使用 Parallel.For 来减少时间。但它导致了一个与下面代码相同的问题:

class Program
    {
        static void Main(string[] args)
        {
            int j=0;
            Parallel.For(0, 10000000, i =>
                {
                    j++;
                });
            Console.WriteLine(j);
            Console.ReadKey();
        }
    } 

每次我运行上面的程序时,它都会在 0 到 10000000 之间写入一个不同的 j 值。我猜它不会等待所有迭代完成。它传递到下一行。 我应该如何解决这个问题?任何帮助将不胜感激。谢谢。

版本: Interlocked.Increment(ref j);子句解决了意想不到的结果,但是当我与正常的 for 循环相比时,这个操作会导致大约 10 倍的时间。

【问题讨论】:

  • 使用Interlocked.Increment(ref j)
  • 这是一个概念验证,还是您真的想将一千万个整数相加?
  • 这只是一个概念验证
  • 不是互锁增量使并行循环花费更长的时间。这是初始化/启动/拆除并行生成的一千万个线程的开销。你无法摆脱这种开销。你能做的就是把真正的工作放在循环体中;当这项工作(例如,遗传算法一步“制作后代并评估它”)比您将通过并行获得的线程管理开销大得多时。如果工作量足够大,下一个陷阱是它们可能需要在共享结构上互锁,这可能会扼杀加速。

标签: c# parallel-processing parallel.for


【解决方案1】:

您可以使用Interlocked.Increment(int32) 方法,这可能是最简单的。

使用Parallel.For 将创建多个线程来执行相同的 lambda 表达式;在这种情况下,它所做的只是j++

j++ 会被编译成类似j = j + 1 的东西,这是一个读写操作。这可能会导致不良行为。

j = 50

线程 1 正在执行对 j++ 的读取,它将获得 50 并将其加 1。在该线程完成对j 的写入操作之前,另一个线程执行读取操作并从j 读取50,然后第一个线程已完成对j 的写入操作使其变为51,但第二个线程在内存中仍有50作为j 的值,并将其加1,然后再次将51 写回j

使用Interlocked 类可确保每个操作都以原子方式发生。

【讨论】:

  • 你可以解释为什么提问者的代码失败了,为什么他对Parallel.For 的猜测不正确,这可能是最有用的
  • 是的,它解决了增量,但会导致更多时间:(
  • @Rawling 我正在写一些东西来解释这一点,为我解释有点困难
  • @user3648161 如果您想拥有一个快速的程序,只需将其更改为Console.WriteLine(10000000);
  • @Servy 你很聪明。我怎么没想到!
【解决方案2】:

您对j 的访问权限未同步。请阅读有关多线程和同步的基础书籍或教程。

Parallel.For 确实等待所有迭代。

使用同步(从而打败了并行的使用):

class Program
{
    static void Main(string[] args)
    {
        object sync = new object;
        int j=0;
        Parallel.For(0, 10000000, i =>
            {
                lock(sync)
                {
                  j++;
                }
            });
        Console.WriteLine(j);
        Console.ReadKey();
    }
} 

【讨论】:

    【解决方案3】:

    Parallel.For 等待所有迭代完成。您在变量中看到意外值的原因是不同的 - 这是意料之中的。

    基本上,Parallel.For 将迭代分派给多个线程(如您所料)。但是,如果没有某种保护机制,多个线程不能共享相同的可写内存——如果它们这样做,您将拥有data race,结果在逻辑上是不确定的。这适用于所有编程语言,也是多线程的基本警告。

    您可以设置多种防护措施,具体取决于您的用例。它们工作的基本方式是通过atomic operations,您可以通过Interlocked 助手类访问它们。更高级别的守卫包括Monitor 类、相关的lock language constructReaderWriterLock 等类(及其兄弟)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多