您的代码当前有一个race condition,这就是结果不正确的原因。为了说明原因,让我们用一个简单的例子:
您在 2 个线程上运行,数组为 int input[4] = {1, 2, 3, 4};。您将 sum 正确初始化为 0 并准备好开始循环。在循环的第一次迭代中,线程 0 和线程 1 从内存中读取sum 为0,然后将它们各自的元素添加到sum,并将其写回内存。但是,这意味着线程 0 正在尝试将 sum = 1 写入内存(第一个元素是 1 和 sum = 0 + 1 = 1),而线程 1 正在尝试将 sum = 2 写入内存(第二个元素是 @987654332 @ 和 sum = 0 + 2 = 2)。此代码的最终结果取决于哪个线程最后完成,因此最后写入内存,这是一种竞争条件。不仅如此,在这种特殊情况下,代码可以产生的答案都不是正确的!有几种方法可以解决这个问题;我将在下面详细介绍三个基本的:
#pragma omp critical:
在 OpenMP 中,有一个称为 critical 的指令。这限制了代码,因此一次只有一个线程可以做某事。例如,你的for-loop 可以写成:
#pragma omp parallel for schedule(static)
for(i = 0; i < snum; i++) {
int *tmpsum = input + i;
#pragma omp critical
sum += *tmpsum;
}
这消除了竞争条件,因为一次只有一个线程访问和写入sum。但是,critical 指令对性能非常不利,并且可能会扼杀您从使用 OpenMP 获得的大部分(如果不是全部)收益。
#pragma omp atomic:
atomic 指令与critical 指令非常相似。主要区别在于,虽然critical 指令适用于您希望一次执行一个线程的任何事情,但atomic 指令仅适用于内存读/写操作。由于我们在此代码示例中所做的只是读取和写入 sum,因此该指令将完美运行:
#pragma omp parallel for schedule(static)
for(i = 0; i < snum; i++) {
int *tmpsum = input + i;
#pragma omp atomic
sum += *tmpsum;
}
atomic 的性能普遍明显优于critical。但是,在您的特定情况下,它仍然不是最佳选择。
reduction:
您应该使用的方法,以及其他人已经建议的方法,是reduction。您可以通过将for-loop 更改为:
#pragma omp parallel for schedule(static) reduction(+:sum)
for(i = 0; i < snum; i++) {
int *tmpsum = input + i;
sum += *tmpsum;
}
reduction 命令告诉 OpenMP,当循环运行时,您希望每个线程跟踪自己的 sum 变量,并在循环结束时将它们全部加起来。这是最有效的方法,因为您的整个循环现在并行运行,唯一的开销是在循环结束时,每个线程的 sum 值需要相加。