我有一个使用POSIX计时器(timer_create()
)的程序。本质上,程序设置一个计时器并开始执行一些冗长的(可能是无限的)计算。当计时器到期并调用信号处理程序时,处理程序将打印出已计算的最佳结果并退出程序。
我考虑使用OpenMP并行进行计算,因为它应该加快它的速度。
在pthreads中,有一些特殊功能,例如为我的线程设置信号掩码等。 OpenMP是否提供此类控制,或者我是否必须接受信号可以传递给OpenMP创建的任何线程的事实?
另外,如果我当前处于代码的并行部分并调用了我的处理程序,它是否仍然可以安全地终止应用程序(exit(0);
)并执行锁定OpenMP锁定等操作?
答案 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(页面末尾)
不允许printf
(write
是)。如果您知道任何线程没有使用printf信号,那么您可以使用printf(例如,并行区域中没有printf)。
它仍可以安全地杀死应用程序(exit(0);)
是的,可以:abort()
和_exit()
来自处理程序。
当任何线程执行exit
或abort
时,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
同步接受阻塞信号之前,仔细检查阻塞信号是否符合我们的期望(除非有一些,否则应立即返回此处)该程序的其他部分正在创建竞争条件)。我们终于设置了相关标志。一些重要的性能注意事项和警告:
omp for
循环不能脱离 1 ,因此,您必须旋转其余的迭代,或使用更基本的OpenMP原语重写循环。可以打破常规循环(例如外部并行循环的内部循环)。这是示例代码:
#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
。