简体   繁体   English

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

[英]parallel.foreach works, but why?

Can anyone explain, why this program is returning the correct value for sqrt_min? 谁能解释,为什么该程序返回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);

It works by sheer luck. 运气很好。 Sometimes when you run it you are lucky that the non-atomic reads and writes to the double are not resulting in "torn" values. 有时,当您运行它时,您很幸运 ,对double的非原子读取和写入不会导致“ torn”值。 Sometimes you are lucky that the non-atomic tests and sets just happen to be setting the correct value when that race happens. 有时,您很幸运 ,当比赛发生时,非原子测试和设置恰好设置了正确的值。 There is no guarantee that this program produces any particular result. 不能保证该程序会产生任何特定的结果。

Your code is not safe; 您的代码不安全; it only works by coincidence. 它只是偶然地起作用。

If two threads run the if simultaneously, one of the minimums will be overwritten: 如果两个线程同时运行if ,则最小值之一将被覆盖:

  • sqrt_min = 6
  • Thread A: sqrt = 5 线程A: sqrt = 5
  • Thread B: sqrt = 4 线程B: sqrt = 4
  • Thread A enters the if 线程A进入if
  • Thread B enters the if 线程B进入if
  • Thread B assigns sqrt_min = 4 线程B分配sqrt_min = 4
  • Thread A assigns sqrt_min = 5 线程A分配sqrt_min = 5

On 32-bit systems, you're also vulnerable to read/write tearing. 在32位系统上,您还容易受到读/写撕裂的攻击。

It would be possible to make this safe using Interlocked.CompareExchange in a loop. 可以在循环中使用Interlocked.CompareExchange使其安全。

For why your original code is broken check the other answers, I won't repeat that. 至于为什么您的原始代码被破坏,请检查其他答案,我不再赘述。

Multithreading is easiest when there is no write access to shared state. 当没有对共享状态的写访问权时,多线程是最容易的。 Luckily your code can be written that way. 幸运的是,您的代码可以用这种方式编写。 Parallel linq can be nice in such situations, but sometimes the the overhead is too large. 在这种情况下,并行linq可能会很好,但是有时开销太大。

You can rewrite your code to: 您可以将代码重写为:

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

In your specific problem it's faster to swap around the Min and the Sqrt operation, which is possible because Sqrt is monotonically increasing. 在您的特定问题中,交换MinSqrt操作更快,这是可能的,因为Sqrt单调增加。

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

Your code does not really work: I ran it in a loop 100,000 times, and it failed once on my 8-core computer, producing this output: 您的代码并没有真正起作用:我在一个循环中运行了100,000次,并且在我的8核计算机上一次失败,产生了以下输出:

minimum: 1

I shortened the runs to make the error appear faster. 我缩短了运行时间,以使错误显示更快。

Here are my modifications: 这是我的修改:

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();
    }
}

This is not a coincidence, considering the lack of synchronization around the reading and writing of a shared variable. 考虑到共享变量读写之间缺乏同步,这不是巧合。

As others have said, this only works based on shear luck. 正如其他人所说,这仅基于运气。 Both the OP and other posters have had trouble actually creating the race condition though. 不过,OP和其他张贴者在创建比赛条件时都遇到了麻烦。 That is fairly easily explained. 这很容易解释。 The code generates lots of race conditions, but the vast majority of them (99.9999% to be exact) are irrelevant. 该代码生成了很多竞争条件,但是其中绝大多数(精确到99.9999%)都是无关紧要的。 All that matters at the end of the day is the fact that 0 should be the min result. 最重要的是,最终结果应为0。 If your code thinks that root 5 is greater than root 6, or that root 234 is greater than root 235 it still won't break. 如果您的代码认为根5大于根6,或者根234大于根235,它仍然不会中断。 There needs to be a race condition specifically with the iteration generating 0. The odds that one of the iterations has a race condition with another is very, very high. 特别需要有一个竞争条件,该迭代会生成0。其中一个迭代具有另一个竞争条件的可能性非常非常高。 The odds that the iteration processing the last item has a race condition is really quite low. 处理最后一项的迭代具有竞争条件的几率确实非常低。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM