我如何处理OpenMP中的数据竞争?

我试图使用OpenMP在数组中添加数字。 以下是我的代码:

int* input = (int*) malloc (sizeof(int)*snum); int sum = 0; int i; for(i=0;i<snum;i++){ input[i] = i+1; } #pragma omp parallel for schedule(static) for(i=0;i<snum;i++) { int* tmpsum = input+i; sum += *tmpsum; } 

这并不能产生正确的结果。 怎么了?

您的代码目前有竞争条件 ,这就是结果不正确的原因。 为了说明这是为什么,让我们使用一个简单的例子:

你正在运行2个线程,数组是int input[4] = {1, 2, 3, 4}; 。 您正确地将sum初始化为0并准备开始循环。 在循环的第一次迭代中,线程0和线程1将内存中的和读取为0 ,然后将它们各自的元素添加到sum ,并将其写回内存。 但是,这意味着线程0试图将sum = 1写入内存(第一个元素为1sum = 0 + 1 = 1 ),而线程1尝试将sum = 2写入内存(第二个元素是2sum = 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更改for

 #pragma omp parallel for schedule(static) reduction(+:sum) for(i = 0; i < snum; i++) { int *tmpsum = input + i; sum += *tmpsum; } 

reduction命令告诉OpenMP,当循环运行时,您希望每个线程跟踪其自己的sum变量,并在循环结束时将它们全部添加。 这是最有效的方法,因为整个循环现在并行运行,唯一的开销是在循环结束时,每个线程的sum值需要加起来。

使用reduction子句( MSDN上的描述 )。

 int* input = (int*) malloc (sizeof(int)*snum); int sum = 0; int i; for(i=0;i