我希望了解这里的含义。为什么这个程序会“挂起”?
来自https://bisqwit.iki.fi/story/howto/openmp/
OpenMP和
fork()
值得一提的是在一个中使用OpenMP 调用fork()
的程序需要特别考虑。这个 问题只影响GCC; ICC不受影响。如果你的程序 打算成为使用daemonize()
或其他的后台流程 类似的方法,你不能在fork之前使用OpenMP功能。 使用OpenMP功能后,只允许使用fork 子进程不使用OpenMP功能,或者它使用OpenMP功能 全新的流程(例如exec()
之后)。这是错误程序的一个例子:
#include <stdio.h> #include <sys/wait.h> #include <unistd.h> void a(){ #pragma omp parallel num_threads(2) { puts("para_a"); // output twice } puts("a ended"); // output once } void b(){ #pragma omp parallel num_threads(2) { puts("para_b"); } puts("b ended"); } int main(){ a(); // Invokes OpenMP features (parent process) int p = fork(); if(!p){ b(); // ERROR: Uses OpenMP again, but in child process _exit(0); } wait(NULL); return 0; }
运行时,此程序挂起,永远不会到达输出“b 由于libgomp API没有,因此目前没有解决方法 指定可用于准备对
fork()
的调用的函数。
答案 0 :(得分:3)
发布的代码违反了POSIX标准。
应使用单个线程创建进程。如果是多线程的 进程调用fork()时,新进程应包含一个副本 调用线程及其整个地址空间,可能包括 互斥和其他资源的状态。 因此,要避免 错误,子进程可能只执行异步信号安全 操作直到调用
exec
函数之一为止。
运行OMP并行化代码显然违反了上述限制。
答案 1 :(得分:3)
为了扩展Andrew Henle的答案,fork(2)
所做的是创建第二个进程,通过写时复制(CoW)内存映射共享调用线程的整个内存空间。子进程处于一种尴尬的境地 - 它是具有相同状态的父线程的副本(除了系统调用的返回值和其他一些东西,如定时器和资源使用计数器)以及对其所有内存和打开文件的访问权限描述符除了进行fork(2)
调用之外没有任何其他执行线程。虽然有一些预防措施,但这可以用作多线程的粗略形式(并且在真正的LWP在Unix中引入之前用于此目的),99%的案例fork(2)
用于单一目的 - 产生子进程而孩子在分叉后立即调用execve(2)
(或标准C库中的一个前端)。认识到这一事实,有一个甚至更为极端的版本vfork(2)
甚至不创建父内存的CoW映射,而是直接使用其页表,有效地创建了独立进程和线程之间的混合。在这种情况下,子进程甚至不允许进行异步信号安全函数调用,因为它在父进程堆栈上运行。
请注意,OpenMP规范不包含与其他线程和/或进程控制机制的任何交互,因此,即使它可能适用于某些OpenMP实现,您的示例也不是正确的OpenMP程序。
答案 2 :(得分:1)
我在以下场景中发现了这一点:
(使用 Python 多线程是行不通的,因为每个并行任务中的代码都有大量 Python 代码,由于 GIL,这些代码本质上是单线程的。出于同样的原因,仅仅串行运行代码,只受益于 C++ 扩展内部的并行化。)
请注意,调用并行函数(例如 numpy corrcoef)确实设法在每个子进程中使用并行处理。大概它没有使用 OpenMP 来做到这一点。
OpenMP 中的“本来很好”具有“重置所有内容,忘记所有先前生成的线程,就好像我们刚刚开始执行一样”。然后我们可以在分叉的子进程上调用这个函数,并在每个子进程中使用 OpenMP(注意减少使用的 OpenMP 线程的数量,以免系统过载)。
答案 3 :(得分:0)
这显然是死锁情景。它正好在下面的代码中发生。因为它在内部再次执行thread / fork()以并行执行puts("para_b");
。它有些被困在死锁中。
#pragma omp parallel num_threads(2)
{
puts("para_b");//in a trap means dead lock
}