【问题标题】:How to find out if inside an openMP parallel region?如何确定是否在 openMP 并行区域内?
【发布时间】:2012-10-23 19:24:34
【问题描述】:

在我的代码中,我想避免从任何 openMP 并行区域内抛出异常(因为如果未在同一区域内捕获,这会导致未处理的异常)。为此,我尝试使用openmp运行时库函数

omp_in_parallel();

决定是抛出异常还是写出错误消息并终止。但是,在 gcc 4.7.0 下,如果并行区域只有一个线程,这将不起作用:

#include <iostream>
#include <omp.h>

void do_something()
{
  if(!omp_in_parallel())           // omp_in_parallel() returns false!
    throw 3;                       // so should be able to safely throw
}

int main()
{
  omp_set_num_threads(1);
  try {
#   pragma omp parallel
    do_something();
  } catch(int e) {
    std::cerr<<"error: '"<<e<<"'\n";  // never gets here
  }
}

不会导致 error: '3' 而是在抛出一个 'int' Abort 的实例后调用 terminate

这是正确的行为(omp_in_parallel())吗? (openMP 标准看起来很模糊)还是 gcc 中的错误?如何修复do_something() 的上述代码,使其仅在不在并行区域时抛出?

【问题讨论】:

    标签: c++ c openmp


    【解决方案1】:

    OpenMP 标准规定,当且仅当封闭的parallel 区域处于活动状态时,omp_in_parallel() 才会返回 true。活动的parallel 区域定义为由多个线程组成的团队执行的区域。在您的情况下,您有一个非活动的并行区域,因为只有一个线程。因此omp_in_parallel() 返回false 并且throw 被执行。之所以出错,是因为 OpenMP 标准将异常限制在同一个并行区域和线程:

    parallel 区域内执行的throw 必须导致在同一parallel 区域内恢复执行,并且引发异常的同一线程必须捕获它。

    这会使您的代码不一致,因为您让异常未经处理地通过并行区域。英特尔 OpenMP 运行时会发出相同的错误,因此它不是 GCC 特定的行为。

    实际发生的情况是 GCC 通过将您的代码包装在带有包罗万象的异常过滤器的 try/catch 块中来转换 OpenMP 区域:

    #pragma omp parallel [child fn: main.omp_fn.0 (???)]
      {
        try
          {
            do_something ();
          }
        catch
          {
            <<<eh_filter (NULL)>>>
              {
                terminate ();
              }
          }
        #pragma omp return
      }
    

    正是这个包罗万象的异常过滤器负责处理终止消息。

    要解决这个问题,整个try/catch 块应该平行区域内,而不是相反:

    # pragma omp parallel
    {
       try {
          do_something();
       } catch(int e) {
          std::cerr<<"error: '"<<e<<"'\n";  // never gets here
       }
    }
    

    (添加的附加块是为了让英特尔 C++ 编译器满意;对于 GCC 并非绝对必要)

    这会按预期输出error: '3'

    编辑:有趣的是你的异常处理程序甚至没有进入最终的二进制文件。即使给定 GCC 的默认优化级别(即使用 g++ -fopenmp -o prog prog.cc 编译),冗余消除器也能够检测到由于隐式内部异常处理程序而永远不会到达您的异常处理程序,因此您的处理程序被删除。然后编译器发现隐式终止处理程序也是多余的,因为整个进程已经有一个顶级异常处理程序执行相同的操作(调用terminate()),因此甚至删除了隐式处理程序。最终的代码如此精简和卑鄙——根本没有异常处理程序:

    ;; Function int main() (main, funcdef_no=970, decl_uid=20816, cgraph_uid=212)
    
    int main() ()
    {
      int e;
      int D.20855;
      struct basic_ostream & D.20854;
      struct basic_ostream & D.20853;
      void * D.20852;
      register int * D.20819;
    
    <bb 2>:
      omp_set_num_threads (1);
      __builtin_GOMP_parallel_start (main._omp_fn.0, 0B, 0);
      main._omp_fn.0 (0B);
      __builtin_GOMP_parallel_end ();
      D.20855_1 = 0;
      // <------ See, ma', no exception handling at all :)
    
    <L0>:
      return D.20855_1;
    
    }
    
    ;; Function <built-in> (main._omp_fn.0, funcdef_no=976, decl_uid=20857, cgraph_uid=222)
    
    <built-in> (void * .omp_data_i)
    {
    <bb 2>:
      do_something ();
      return;
      // <------ See, ma', they've nuked the implicit termination handler
    
    }
    

    人们会爱上-fdump-tree-all GCC 选项。

    编辑:关于如何修复do_something() 的问题 - 使用omp_get_level() 而不是omp_in_parallel()

    void do_something()
    {
       if(omp_get_level() == 0)
         throw 3;
    }
    

    omp_get_level() 返回包含调用的parallel 区域的嵌套级别,无论它们是否处于活动状态。

    【讨论】:

    • +1 从未使用过 OpenMP 异常,也不知道 -fdump-tree-all!
    • 所以我该如何修复do_something()的答案基本上是:你不能。无法确定是否从 openMP 并行区域内调用函数,即使信息在某处(但不可访问)。对吗?
    • @Walter,对不起,我没有看到问题:) 当然你可以解决它。现在扩展我的答案...
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多