Parallel.For不循环预期的时间

本文关键字:时间 循环 For Parallel | 更新日期: 2023-09-27 17:58:46

我有这个测试:

public void Run()
    {
        var result = new List<int>();
        int i = 0;
        Parallel.For(0, 100000, new Action<int>((counter) =>
        {
            i++;
            if (counter == 99999)
            {
                Trace.WriteLine("i is " + i);
            }
        }));
    }

现在,为什么输出打印的似乎是50000到99999范围内的随机数字?我预计输出总是99999。我是否误解了并行循环实现?

如果我现在运行循环100次,那么程序输出100,正如预期的那样。仅供参考我有一个8核CPU

更新:当然!我错过了它的线程安全方面:)谢谢!现在让我们看看使用锁、声明为volatile或使用Interlocked

Parallel.For不循环预期的时间

哪个更快

您的问题可能是由于i++不是线程安全的,并且您的任务处于某种"竞争条件"。

关于i++不是线程安全的进一步解释,你可以在这里找到:递增器/递减器(var++,var-)等线程安全吗?

引用Michael Burr在上述链接线程中给出的答案(在那里投票支持):

根据您的站台在.NET上,您可以使用Interlocked类方法(例如Interlocked.Increment())。

Rob Kennedy提到,即使在对于单个INC指令,就内存而言执行一组读取/增量/写入步骤。有多处理器系统上的腐败机会。

还有一个不稳定的问题,这将是使操作线程安全-但是,标记变量volatile不足以保证线程安全。使用联锁平台提供的支持。

这在一般情况下是正确的,当然在x86/x64平台上也是如此。


Trace.WriteLine()的竞争

在执行++i和输出i之间,其他并行任务可能已多次更改/增加i

想象一下你的第一个任务,它是递增i,使其变为1。然而,根据您的运行时环境和当天的天气,在第一个任务输出变量之前,i可能会被其他并行任务增加20倍——现在是21(不再是1)。为了防止这种情况发生,请使用一个本地变量,该变量会记住特定任务的递增i的值,以便稍后处理/输出:

int remember = Interlocked.Increment(ref i);
...
Trace.WriteLine("i of this task is: " + remember);

因为您的代码不是线程安全的。i++是"读-修改-写",这不是线程安全的。

请使用Interlocked.Increment,或者将其锁定。

++运算符线程安全吗?

Parallel.For意味着它以并行的方式运行所有任务,所以对于代码来说,这并不意味着它从0到100000运行,它可以先用99999开始运行委托函数,所以这就是为什么你得到一个任意的i值。

当并行循环运行时,TPL对数据源进行分区,以便循环可以同时对多个部分进行操作。在后台,任务调度器根据系统资源和工作负载对任务进行分区。在可能的情况下,如果工作负载变得不平衡,调度程序会在多个线程和处理器之间重新分配工作。