关于c#:使用嵌套的Parallel.For

Using nested Parallel.For

请考虑以下示例:

1
2
3
4
5
6
7
var x = 0;

for (var i = 0; i < 100; i++ )
{
    for (var a = i+1; a < 100; a++)
        x += 1;
}

打印x时总是得到4950。如果我想并行化怎么办?

这就是我想出的

1
Parallel.For(0, 100, i => Parallel.For(i + 1, 100, a => { x += 1; }));

但是,每次我运行它都不会打印4950。为什么?


Parallel Extensions以几乎命令式的语法帮助您完成任务的创建,分配,运行和集合。它没有做的是照顾每种线程安全(陷阱之一)。您试图使并行线程同时更新单个共享变量。要正确执行此类操作,您必须先介绍例如锁定。

我不确定您要做什么。我假设您的代码只是一个占位符或实验。并行化仅在您可以隔离不同的工作时才适用。而不是在您经常需要共享数据的时候。


这将是执行此操作的"正确"方法,这将不需要您锁定最终的总对象,而仅需要您在每个本地线程循环结束时执行互锁操作。

1
2
3
4
5
6
7
8
9
10
11
12
int x = 0;
Parallel.For(0, 100,
    () => 0, //LocalInit
    (i, loopstate, outerlocal) =>
    {
        Parallel.For(i + 1, 100,
            () => 0, //LocalInit
            (a, loopState, innerLocal) => { return innerLocal + 1; },
            (innerLocal) => Interlocked.Add(ref outerlocal, innerLocal)); //Local Final
        return outerlocal;
    },
    (outerLocal) => Interlocked.Add(ref x, outerLocal)); //Local Final

但是,让两个嵌套的Parallel语句完成这些工作很少是一个好主意。有一个开销成本需要考虑,如果您要做的工作量如此之小,最好只执行一个Parallel语句,或者根本不执行任何语句。

我强烈建议您下载并阅读《并行编程模式》,它详细介绍了为什么这样的小嵌套并行循环不是一个好主意。


作为每次锁定的替代方法,可以将线程局部变量与锁结合使用:

1
2
3
4
5
6
7
8
9
Object thisLock = new Object();
var globalSum = 0;
System.Threading.Tasks.Parallel.For(0, 100, i => {
    System.Threading.Tasks.Parallel.For(i + 1, 100, () => 0, (num, loopState, subSum) => ++subSum, subSum => {
        lock(thisLock) { globalSum += subSum; }
    });
});

Console.WriteLine(globalSum);