5

在我的代码中,我想避免从任何 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
  }
}

不会导致错误:'3',但在抛出 'int' Abort 的实例后调用终止

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

4

1 回答 1

9

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

throw在一个区域内执行parallel必须导致在同一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-allGCC 选项。

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

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

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

于 2012-10-22T13:57:32.877 回答