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

时间:2014-11-18 15:42:51

标签: c for-loop 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;
  }

这不会为sum产生正确的结果。怎么了?

2 个答案:

答案 0 :(得分:9)

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

您正在运行2个线程,阵列为int input[4] = {1, 2, 3, 4};。您正确地将sum初始化为0并准备开始循环。在循环的第一次迭代中,线程0和线程1从内存中读取sum0,然后将它们各自的元素添加到sum,并将其写回内存。但是,这意味着线程0正在尝试将sum = 1写入内存(第一个元素是1sum = 0 + 1 = 1),而线程1正在尝试将sum = 2写入内存(第二个元素是2sum = 0 + 2 = 2)。此代码的最终结果取决于哪个线程最后完成,因此最后写入内存,这是一个竞争条件。不仅如此,在这种特殊情况下,代码所能产生的答案都不正确!有几种方法可以解决这个问题;我将在下面详述三个基本内容:

<强> #pragma omp critical

在OpenMP中,有一个所谓的critical指令。这限制了代码,以便一次只有一个线程可以执行某些操作。例如,您的for - 循环可以写成:

#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 - 循环更改为:

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

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

答案 1 :(得分:3)

使用reduction子句(description at MSDN)。

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) reduction(+:sum)
for(i=0;i<snum;i++)
{
    sum += input[i];
}