了解关于fork

时间:2018-03-01 12:04:55

标签: c openmp

我希望了解这里的含义。为什么这个程序会“挂起”?

来自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()的调用的函数。

4 个答案:

答案 0 :(得分:3)

发布的代码违反了POSIX标准。

POSIX fork() standard states

  

应使用单个线程创建进程。如果是多线程的   进程调用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
  • 实现 C++ python 扩展
  • 在此扩展程序中使用 OpenMP
  • 使用 Python 多处理并行执行(似乎使用 fork 和 copy-on-write 在子进程之间提供“免费”的穷人只读共享内存)。
  • 在子流程中调用扩展 => 死锁。

(使用 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
 }