OPENMP迭代永远不会退出



我正在尝试创建一个迭代过程的代码,同时保持while在并行区域内,以尽量减少并行化开销

代码是这样的问题是它永远不会退出,所以如果可能的话,我想知道你对这个

的想法
#include <stdio.h>
#include <omp.h>
int main(int argc, char **argv)
{
    float error = 20;
#pragma omp parallel shared(error)
    {
        while (error > 5)
        {
#pragma omp for  reduction(-:error)
            for (int i=0; i<10; ++i)
            {
                error -= 1;
            }
        }
    }
    fprintf(stderr, "Program terminatedn");
    return 0;
}

这是一个有趣的小问题。我没有压倒性的openmp经验,但在对你的代码进行了一些实验之后,我认为问题是由于进入并行for循环(插入write语句以"观察"你的代码)时缺乏同步引起的。

您可以通过在并行for循环之前插入一个屏障来使代码工作:

#pragma omp barrier
#pragma omp for reduction(-:error)
   for(int i=0; i<10; ++i)

如果没有这个屏障,并且运行在两个线程上,一个线程将第二次进入for循环,并将error降低到5,这时另一个线程根本不会进入第二个for循环,使系统处于一个线程执行并行for循环,但另一个线程拒绝加入的奇怪状态。在并行循环中写入共享变量,将它们用作其他地方的控制变量,这无疑是一个警告。

您的程序有未指定行为。参见OpenMP 5.0规范中的2.8节1:

每个工作共享区域必须由团队中的所有线程遇到,或者根本没有线程遇到

这意味着任何类型的分支(if, while等)#pragma omp for(或任何其他工作共享构造)周围的不同线程的条件可能不同:

#pragma omp parallel
{
  if (...true for some threads, false for others...) // ILLEGAL!
  {
    #pragma omp for
    for (...) ...
  }
  while (...true for some threads, false for others...) // ILLEGAL!
  {
    #pragma omp for
    for (...) ...
  }
}

在您的示例中,此未指定的行为可能导致以下事件序列:

  • 每个线程检查条件,但可能不是所有线程都一样-有些进入while循环,有些没有。
  • 如果进入while循环:
    • 他们遇到了#pragma omp for .
    • 在for循环中,他们更新error
  • 它们在#pragma omp for的隐式屏障处等待。
  • 如果它们没有进入while循环:
    • 它们在#pragma omp parallel的隐式屏障处等待。
  • 当一个OpenMP线程到达一个屏障时,它等待,直到它的团队中的所有线程都到达屏障。 #pragma omp for的隐式屏障不适应遇到该构造的线程数量。在您的例子中,一些线程永远不会到达for循环结束时的屏障(因为对它们来说while条件为假)。它们已经跳过了while循环,现在在#pragma omp parallel末尾的隐式屏障处等待。

    结果是一个死锁:一些线程在#pragma omp for的末尾等待,另一些在#pragma omp parallel的末尾等待,这两个组永远不会再聚集在一起…


    在Walter的回答中建议的#pragma omp for之前的显式屏障通过分离共享变量error的读和写来解决这个问题。更具体地说:

    • 每个线程检查条件,并且对所有线程都是一样的-进入while循环体的是all或none。
    • 如果进入while循环:
      • 它们都在显式屏障处等待。
      • 他们都遇到了#pragma omp for .
      • 在for循环中,他们更新error .
    • 它们都在#pragma omp for末尾的隐式屏障处等待。(屏障执行隐式flush,这意味着所有线程都看到error的最终值。)
    • 返回开始
  • while循环后:
    • 它们都在#pragma omp parallel末尾的隐式屏障处等待。
  • 当然,现在所有线程都执行for循环,这并没有"最小化并行化开销",这是你想要的。我猜你将不得不重新构造你的代码来达到这个目标。也许使用#pragma omp task代替#pragma omp for可能是一个好方法,但这取决于实际数据结构和算法的细节。


    注意:您可以通过向#pragma omp for添加nowait子句来摆脱死锁,但这将是一个hack,并且您的程序仍然会有未指定的行为

    <子> 1:……或其他OpenMP版本中的相应部分。

    最新更新