任何人都可以解释为什么这个程序返回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);
答案 0 :(得分:13)
纯粹运气。有时当你运行它时,你是幸运,非原子读取和写入double不会导致“撕裂”值。有时候你是幸运,非原子测试和设置恰好在该竞赛发生时设置了正确的值。无法保证此程序产生任何特定结果。
答案 1 :(得分:5)
您的代码不安全;它只能巧合。
如果两个线程同时运行if
,其中一个最小值将被覆盖:
sqrt_min = 6
sqrt = 5
sqrt = 4
if
if
sqrt_min = 4
sqrt_min = 5
在32位系统上,您也容易受到读/写撕裂的影响。
可以在循环中使用Interlocked.CompareExchange
使其安全。
答案 2 :(得分:4)
为什么你的原始代码被破坏了,请查看其他答案,我不再重复了。
当没有对共享状态的写访问权时,多线程是最简单的。幸运的是,您的代码可以这样编写。在这种情况下,并行linq可能很好,但有时开销太大。
您可以将代码重写为:
double sqrt_min = myArr.AsParallel().Select(x=>Math.Sqrt(x)).Min();
在您的具体问题中,交换Min
和Sqrt
操作会更快,这可能是因为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.迭代之一与另一个迭代具有竞争条件的几率非常非常高。迭代处理最后一个项目的竞争条件非常低。