我有这个测试:
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.我是否想念并行for循环实现?
如果我现在运行循环100次,则程序输出100,如预期的那样。仅供我使用8核CPU
更新: 当然!我错过了它的线程安全方面:)谢谢!现在让我们看看哪一个更快,使用锁定,声明为volatile,或使用Interlocked
答案 0 :(得分:3)
您的问题可能是由于i++
不是线程安全的,而且您的任务处于某种“竞争状态”。
有关i ++不是线程安全的进一步说明,请访问:Are incrementers / decrementers (var++, var--) etc thread safe?
Michael Burr在前面提到的链接线程中给出的答案的引用(在那里赞成):
你可以使用InterlockedIncrement()之类的东西,具体取决于你的 平台。在.NET上,您可以使用Interlocked类方法 (例如Interlocked.Increment())。
Rob Kennedy提到,即使操作是按照术语实施的 一个INC指令,就内存而言a 执行读取/递增/写入步骤集。有 多处理器系统腐败的机会。
还有一个不稳定的问题,这将是一个必要的部分 使操作线程安全 - 但是,标记变量 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);
答案 1 :(得分:1)
因为您的代码不是线程安全的。 i++
是“读取修改写入”,这不是线程安全的。
改为使用Interlocked.Increment
,或锁定它。
答案 2 :(得分:0)
Parallel.For意味着它以并行方式运行所有任务,因此对于代码来说,它并不意味着它从0到100000运行,它可以先用99999开始运行委托函数,这就是为什么你得到一个任意的i值。
当并行循环运行时,TPL对数据源进行分区,以便循环可以同时在多个部分上运行。在幕后,任务计划程序根据系统资源和工作负载对任务进行分区。如果可能,调度程序会在工作负载变得不平衡的情况下在多个线程和处理器之间重新分配工作。