OpenMP并行程序中的信号处理

时间:2011-11-15 14:47:58

标签: c pthreads signals openmp signal-handling

我有一个使用POSIX计时器(timer_create())的程序。本质上,程序设置一个计时器并开始执行一些冗长的(可能是无限的)计算。当计时器到期并调用信号处理程序时,处理程序将打印出已计算的最佳结果并退出程序。

我考虑使用OpenMP并行进行计算,因为它应该加快它的速度。

在pthreads中,有一些特殊功能,例如为我的线程设置信号掩码等。 OpenMP是否提供此类控制,或者我是否必须接受信号可以传递给OpenMP创建的任何线程的事实?

另外,如果我当前处于代码的并行部分并调用了我的处理程序,它是否仍然可以安全地终止应用程序(exit(0);)并执行锁定OpenMP锁定等操作?

2 个答案:

答案 0 :(得分:2)

OpenMP 3.1标准对信号一无所知。

据我所知,Linux / UNIX上的每个流行的OpenMP实现都基于pthread,因此OpenMP线程是pthread的线程。并且适用于pthreads和信号的通用规则。

  

OpenMP是否提供此类控制

没有任何具体控制;但你可以尝试使用pthread的控件。唯一的问题是要知道使用了多少OpenMP线程以及在何处放置控制语句。

  

信号可以传递给OpenMP创建的任何线程吗?

默认情况下,是的,它将被传递给任何线程。

  

我的处理程序被调用,

关于信号处理程序的常规规则仍然适用。信号处理程序中允许的函数列在http://pubs.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_04.html(页面末尾)

不允许printfwrite是)。如果您知道任何线程没有使用printf信号,那么您可以使用printf(例如,并行区域中没有printf)。

  

它仍可以安全地杀死应用程序(exit(0);)

是的,可以:abort()_exit()来自处理程序。

当任何线程执行exitabort时,Linux / Unix将终止所有线程。

  

并执行锁定OpenMP锁的操作?

你不应该,但是如果你知道在信号处理程序运行时这个锁不会被锁定,你可以尝试这样做。

!!更新

有一个采用信令到OpenMP http://www.cs.colostate.edu/~cs675/OpenMPvsThreads.pdf的例子(“OpenMP与C / C ++中的线程”)。简而言之:在处理程序中设置一个标志,并在每个第N次循环迭代中在每个线程中添加对该标志的检查。

  

使基于信号的异常机制适应并行区域

     

使用C / C ++发生更多事情   使用Fortran应用程序的应用程序就是这样   该程序使用复杂的用户界面。   Genehunter是用户的一个简单示例   可能会中断一个家族树的计算   通过按下control-C使它可以继续   关于该临床数据库的下一个家谱   疾病。提前终止处理   C ++之类的异常串行版本   涉及信号处理程序的机制,setjump,   和longjump.OpenMP不允许非结构化控制   流过并行构造边界。我们   修改了OpenMP中的异常处理   通过将中断处理程序更改为一个版本   投票机制。捕获的线程   control-C信号设置共享标志。所有线程   检查循环开头的标志   调用例程has_hit_interrupt()   并且如果设置了则跳过迭代。当循环   结束,主人检查国旗,很容易   执行longjump来完成   特殊退出(见图1)

答案 1 :(得分:2)

这有点晚了,但是希望这个示例代码能够帮助处于类似位置的其他人!


如osgx所述,OpenMP在信号问题上保持沉默,但是由于OpenMP通常在POSIX系统上使用pthread实现,因此我们可以使用pthread信号方法。

对于使用OpenMP进行繁重的计算,很可能只有少数几个位置可以安全地暂停计算。因此,对于想要获得过早结果的情况,我们可以使用同步信号处理来安全地执行此操作。另一个好处是,这使我们可以接受来自特定OpenMP线程的信号(在下面的示例代码中,我们选择主线程)。捕获信号后,我们只需设置一个标志即可指示应停止计算。然后,每个线程都应确保在方便时定期检查此标志,然后包装其应分担的工作量。

通过使用这种同步方法,我们可以使计算正常退出并且对算法的更改很小。另一方面,所需的信号处理程序方法可能不合适,因为可能很难将每个线程的当前工作状态整理为一致的结果。同步方法的一个缺点是,计算可能要花费大量时间才能停止。

信号检查装置包括三个部分:

  • 阻止相关信号。应该在omp parallel区域之外完成此操作,以便每个OpenMP线程(pthread)都将继承相同的阻塞行为。
  • 轮询来自主线程的所需信号。人们可以为此使用sigtimedwait,但是某些系统(例如MacOS)不支持此功能。更可移植的是,我们可以使用sigpending来轮询任何阻塞信号,然后在使用sigwait同步接受阻塞信号之前,仔细检查阻塞信号是否符合我们的期望(除非有一些,否则应立即返回此处)该程序的其他部分正在创建竞争条件)。我们终于设置了相关标志。
  • 我们应该最后去除信号屏蔽(可选地,对信号进行最后检查)。

一些重要的性能注意事项和警告:

  • 假设每个内部循环迭代很小,则执行信号检查syscall的开销很大。在示例代码中,我们每1000万次(每线程)迭代仅检查一次信号,这大约相当于几秒钟的时间。
  • omp for循环不能脱离 1 ,因此,您必须旋转其余的迭代,或使用更基本的OpenMP原语重写循环。可以打破常规循环(例如外部并行循环的内部循环)。
  • 如果仅主线程可以检查信号,则这可能在主线程比其他线程先完成的程序中产生问题。在这种情况下,这些其他线程将不间断。为了解决这个问题,您可以在每个线程完成工作负荷时“通过检查信号的指挥棒”,或者可以迫使主线程继续运行并进行轮询,直到所有其他线程完成 2
  • 在某些体系结构(例如NUMA HPC)上,检查“全局”信号标志的时间可能会非常昂贵,因此在决定何时何地检查或操作标志时要格外小心。例如,对于旋转循环部分,可能希望在标记变为true时在本地缓存该标记。

这是示例代码:

#include <signal.h>

void calculate() {
    _Bool signalled = false;
    int sigcaught;
    size_t steps_tot = 0;

    // block signals of interest (SIGINT and SIGTERM here)
    sigset_t oldmask, newmask, sigpend;
    sigemptyset(&newmask);
    sigaddset(&newmask, SIGINT);
    sigaddset(&newmask, SIGTERM);
    sigprocmask(SIG_BLOCK, &newmask, &oldmask);

    #pragma omp parallel
    {
        int rank = omp_get_thread_num();
        size_t steps = 0;

        // keep improving result forever, unless signalled
        while (!signalled) {
            #pragma omp for
            for (size_t i = 0; i < 10000; i++) {
                // we can't break from an omp for loop...
                // instead, spin away the rest of the iterations
                if (signalled) continue;

                for (size_t j = 0; j < 1000000; j++, steps++) {
                    // ***
                    // heavy computation...
                    // ***

                    // check for signal every 10 million steps
                    if (steps % 10000000 == 0) {

                        // master thread; poll for signal
                        if (rank == 0) {
                            sigpending(&sigpend);
                            if (sigismember(&sigpend, SIGINT) || sigismember(&sigpend, SIGTERM)) {
                                if (sigwait(&newmask, &sigcaught) == 0) {
                                    printf("Interrupted by %d...\n", sigcaught);
                                    signalled = true;
                                }
                            }
                        }

                        // all threads; stop computing
                        if (signalled) break;
                    }
                }
            }
        }

        #pragma omp atomic
        steps_tot += steps;
    }

    printf("The result is ... after %zu steps\n", steps_tot);

    // optional cleanup
    sigprocmask(SIG_SETMASK, &oldmask, NULL);
}

如果使用C ++,您可能会发现以下类很有用...

#include <signal.h>
#include <vector>

class Unterminable {
    sigset_t oldmask, newmask;
    std::vector<int> signals;

public:
    Unterminable(std::vector<int> signals) : signals(signals) {
        sigemptyset(&newmask);
        for (int signal : signals)
            sigaddset(&newmask, signal);
        sigprocmask(SIG_BLOCK, &newmask, &oldmask);
    }

    Unterminable() : Unterminable({SIGINT, SIGTERM}) {}

    // this can be made more efficient by using sigandset,
    // but sigandset is not particularly portable
    int poll() {
        sigset_t sigpend;
        sigpending(&sigpend);
        for (int signal : signals) {
            if (sigismember(&sigpend, signal)) {
                int sigret;
                if (sigwait(&newmask, &sigret) == 0)
                    return sigret;
                break;
            }
        }
        return -1;
    }

    ~Unterminable() {
        sigprocmask(SIG_SETMASK, &oldmask, NULL);
    }
};

然后可以将calculate()的阻塞部分替换为Unterminable unterm();,并将信号检查部分替换为if ((sigcaught = unterm.poll()) > 0) {...}。当unterm超出范围时,将自动执行解锁信号。


1 严格来说,这不是正确的。 OpenMP支持以cancellation points形式执行“并行中断”的有限支持。如果选择在并行循环中使用抵消点,请确保确切知道隐式抵消点的位置,以便确保计算数据在抵消时保持一致。

2 就我个人而言,我会统计有多少线程完成了for循环,如果主线程完成了循环而没有捕获到信号,它将继续轮询信号,直到它捕获信号或所有线程完成循环。为此,请确保将for循环标记为nowait