这是race condition 的经典示例。您的每个 openmp 线程都在同时访问和更新一个共享值,并且无法保证某些更新不会丢失(最好的情况)或得到的答案不会是乱码(最坏的情况)。
竞态条件的问题在于它们敏感地依赖于时间;在较小的情况下(例如,使用较小的 NREC 和 NLIG),您有时可能会错过这一点,但在较大的情况下,它最终总会出现。
没有#pragma omp for,你得到错误答案的原因是,一旦你进入并行区域,你所有的openmp线程都会启动;除非您使用类似 omp for(所谓的工作共享结构)的东西来拆分工作,否则每个线程将在并行部分执行 所有操作 - 所以所有线程都将执行相同的操作全部金额,全部同时更新S2。
您必须小心 OpenMP 线程更新共享变量。 OpenMP 具有atomic 操作,允许您安全地修改共享变量。下面是一个例子(不幸的是,你的例子对求和顺序非常敏感,很难看出发生了什么,所以我稍微改变了你的总和:)。在mysumallatomic中,每个线程都像以前一样更新S2,但这次是安全完成的:
#include <omp.h>
#include <math.h>
#include <stdio.h>
double mysumorig() {
double S2 = 0;
int a, b;
for(a=0;a<128;a++){
for(b=0;b<128;b++){
S2=S2+a*b;
}
}
return S2;
}
double mysumallatomic() {
double S2 = 0.;
#pragma omp parallel for shared(S2)
for(int a=0; a<128; a++){
for(int b=0; b<128;b++){
double myterm = (double)a*b;
#pragma omp atomic
S2 += myterm;
}
}
return S2;
}
double mysumonceatomic() {
double S2 = 0.;
#pragma omp parallel shared(S2)
{
double mysum = 0.;
#pragma omp for
for(int a=0; a<128; a++){
for(int b=0; b<128;b++){
mysum += (double)a*b;
}
}
#pragma omp atomic
S2 += mysum;
}
return S2;
}
int main() {
printf("(Serial) S2 = %f\n", mysumorig());
printf("(All Atomic) S2 = %f\n", mysumallatomic());
printf("(Atomic Once) S2 = %f\n", mysumonceatomic());
return 0;
}
然而,原子操作确实会损害并行性能(毕竟,重点是防止围绕变量S2!进行并行操作)所以更好的方法是进行求和,并且只在两次求和之后执行原子操作,而不是执行 128*128 次;这就是mysumonceatomic() 例程,每个线程只会产生一次同步开销,而不是每个线程 16k 次。
但这是一个很常见的操作,没有必要自己实现。可以将 OpenMP 内置功能用于归约操作(归约是一种操作,例如计算列表的总和、查找列表的最小值或最大值等,只需查看到目前为止的结果和下一个元素)如@ejd 所建议的那样。 OpenMP 可以运行并且速度更快(它的优化实现比您自己使用其他 OpenMP 操作可以执行的要快得多)。
如您所见,两种方法都有效:
$ ./foo
(Serial) S2 = 66064384.000000
(All Atomic) S2 = 66064384.000000
(Atomic Once) S2 = 66064384.00000