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
您的问题可能是由于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对数据源进行分区,以便循环可以同时对多个部分进行操作。在后台,任务调度器根据系统资源和工作负载对任务进行分区。在可能的情况下,如果工作负载变得不平衡,调度程序会在多个线程和处理器之间重新分配工作。