单个构造中的原子

atomic inside a single construct

在 openMP 框架中,假设我有一系列任务应该由一个任务完成。每个任务都是不同的,所以我无法适应 #pragma omp for 结构。在单个结构中,每个任务更新一个由所有任务共享的变量。我怎样才能保护这样一个变量的更新?

一个简化的例子:

#include <vector>

struct A {
  std::vector<double> x, y, z;
};

int main()
{
  A r;
  #pragma omp single nowait
  {
    std::vector<double> res;
    for (int i = 0; i < 10; ++i)
      res.push_back(i);
    // DANGER
    r.x = std::move(res);
  }
  #pragma omp single nowait
  {
    std::vector<double> res;
    for (int i = 0; i < 10; ++i)
      res.push_back(i * i);
    // DANGER
    r.y = std::move(res);
  }
  #pragma omp single nowait
  {
    std::vector<double> res;
    for (int i = 0; i < 10; ++i)
      res.push_back(i * i + 2);
    // DANGER
    r.z = std::move(res);
  }
  #pragma omp barrier

  return 0;
}

// DANGER 下面的代码行有问题,因为它们修改了共享变量的内存内容。 在上面的示例中,它可能仍然可以正常工作,因为我正在有效地修改 r 的不同成员。问题仍然是:如何确保任务不会同时更新 r?单个构造是否有“某种”atomic 编译指示?

each task updates a variable shared by all tasks.

实际上他们没有。考虑像这样重写代码(不需要临时向量):

void foo( std::vector<double>& x, std::vector<double>& y, std::vector<double>& z) {
  #pragma omp single nowait
  {
    for (int i = 0; i < 10; ++i)
      x.push_back(i);
  }
  #pragma omp single nowait
  {
    for (int i = 0; i < 10; ++i)
      y.push_back(i * i);
  }
  #pragma omp single nowait
  {
    for (int i = 0; i < 10; ++i)
      z.push_back(i * i + 2);
  }
  #pragma omp barrier
}

只要调用者能保证xyz不引用同一个对象,就不存在数据竞争。代码的每一部分都修改了一个单独的向量。无需同步。

现在,这些向量来自何处并不重要。您可以这样调用函数:

 A r;
 foo(r.x, r.y, r.z);

PS:我对 omp 不熟悉了,我假设注释正确地做了你想让它们做的事情。

您的原始代码中没有数据竞争,因为 xyz 是结构 A 中的不同向量(正如@已经强调的那样463035818_is_not_a_number), 因此在这方面您不必更改代码中的任何内容。

但是,您的代码中缺少 #pragma omp parallel 指令,因此目前它是一个串行程序。所以,它应该是这样的:

  #pragma omp parallel num_threads(3)
  {
    #pragma omp single nowait
    {
        std::vector<double> res;
        for (int i = 0; i < 10; ++i)
        res.push_back(i);
        // DANGER
        r.x = std::move(res);
    }
    #pragma omp single nowait
    {
        std::vector<double> res;
        for (int i = 0; i < 10; ++i)
        res.push_back(i * i);
        // DANGER
        r.y = std::move(res);
    }
    #pragma omp single nowait
    {
        std::vector<double> res;
        for (int i = 0; i < 10; ++i)
        res.push_back(i * i + 2);
        // DANGER
        r.z = std::move(res);
    }
  }

在这种情况下,#pragma omp barrier 不是必需的,因为在平行区域的末端有一个隐含的屏障。请注意,我使用了 num_threads(3) 子句来确保只有 3 个线程分配给该并行区域。如果您跳过此子句,那么所有其他线程都将在屏障处等待。

在实际数据竞争的情况下(即不止一个 region/section 更改同一个结构成员),您可以使用 #pragma omp critical (name) 来纠正这一点。但是请记住,当临界区旁边没有足够的真正并行工作时,这种序列化可以抵消多线程的好处。

请注意,更好的解决方案是使用 #pragma omp sections(如@PaulG 所建议)。如果 运行 并行的任务数在编译时已知 sections 是 OpenMP 中的典型选择:

#pragma omp parallel sections 
{
   #pragma omp section 
   {
     //Task 1 here
   }
   #pragma omp section
   {
     //Task 2 
   }
   #pragma omp section
   {
     // Task 3
   }
}

郑重声明,我想证明 #pragma omp for 也很容易做到:

 #pragma omp parallel for 
 for(int i=0;i<3;i++)
 {
    if (i==0)
    {
       // Task 1
    } else if (i==1)
    {
       // Task 2 
    }
    else if (i==2)
    {
       // Task 3
    }
 }