parallel.foreach有效,但为什么呢?

时间:2012-02-06 19:44:20

标签: c# .net thread-safety race-condition parallel.foreach

任何人都可以解释为什么这个程序返回sqrt_min的正确值?

int n = 1000000;

double[] myArr = new double[n];
for(int i = n-1 ; i>= 0; i--){ myArr[i] = (double)i;}

// sqrt_min contains minimal sqrt-value
double sqrt_min = double.MaxValue;

Parallel.ForEach(myArr, num =>
{
double sqrt = Math.Sqrt(num); // some time consuming calculation that should be parallized
if(sqrt < sqrt_min){ sqrt_min = sqrt;}
});
Console.WriteLine("minimum: "+sqrt_min);

5 个答案:

答案 0 :(得分:13)

纯粹运气。有时当你运行它时,你是幸运,非原子读取和写入double不会导致“撕裂”值。有时候你是幸运,非原子测试和设置恰好在该竞赛发生时设置了正确的值。无法保证此程序产生任何特定结果。

答案 1 :(得分:5)

您的代码不安全;它只能巧合。

如果两个线程同时运行if,其中一个最小值将被覆盖:

  • sqrt_min = 6
  • 主题A:sqrt = 5
  • 主题B:sqrt = 4
  • 主题A进入if
  • 主题B进入if
  • 主题B分配sqrt_min = 4
  • 主题A分配sqrt_min = 5

在32位系统上,您也容易受到读/写撕裂的影响。

可以在循环中使用Interlocked.CompareExchange使其安全。

答案 2 :(得分:4)

为什么你的原始代码被破坏了,请查看其他答案,我不再重复了。

当没有对共享状态的写访问权时,多线程是最简单的。幸运的是,您的代码可以这样编写。在这种情况下,并行linq可能很好,但有时开销太大。

您可以将代码重写为:

double sqrt_min = myArr.AsParallel().Select(x=>Math.Sqrt(x)).Min();

在您的具体问题中,交换MinSqrt操作会更快,这可能是因为Sqrt单调增加。

double sqrt_min = Math.Sqrt(myArr.AsParallel().Min())

答案 3 :(得分:3)

你的代码没有真正起作用:我循环运行了100,000次,在我的8核计算机上失败了一次,产生了这个输出:

minimum: 1

我缩短了运行时间以使错误显得更快。

以下是我的修改:

static void Run() {
    int n = 10;

    double[] myArr = new double[n];
    for (int i = n - 1; i >= 0; i--) { myArr[i] = (double)i*i; }

    // sqrt_min contains minimal sqrt-value
    double sqrt_min = double.MaxValue;

    Parallel.ForEach(myArr, num => {
        double sqrt = Math.Sqrt(num); // some time consuming calculation that should be parallized
        if (sqrt < sqrt_min) { sqrt_min = sqrt; }
    });
    if (sqrt_min > 0) {
        Console.WriteLine("minimum: " + sqrt_min);
    }
}


static void Main() {
    for (int i = 0; i != 100000; i++ ) {
        Run();
    }
}

考虑到读取和写入共享变量缺乏同步,这不是巧合。

答案 4 :(得分:2)

正如其他人所说,这只能基于剪切运气。虽然OP和其他海报都在实际创造竞争条件方面遇到了麻烦。这很容易解释。代码会产生很多竞争条件,但绝大多数竞争条件(确切地说是99.9999%)都无关紧要。在一天结束时最重要的是0应该是最小结果。如果您的代码认为root 5大于root 6,或者root 234大于root 235,那么它仍然不会中断。需要有一个竞争条件,特别是迭代生成0.迭代之一与另一个迭代具有竞争条件的几率非常非常高。迭代处理最后一个项目的竞争条件非常低。