了解关于fork的OpenMP缺点

问题描述 投票:2回答:3

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

来自https://bisqwit.iki.fi/story/howto/openmp/

OpenMP和fork()值得一提的是,在调用fork()的程序中使用OpenMP需要特别考虑。这个问题只影响GCC; ICC不受影响。如果您的程序打算使用daemonize()或其他类似方法成为后台进程,则不得在fork之前使用OpenMP功能。在使用OpenMP功能之后,只有子进程不使用OpenMP功能时才允许使用fork,或者只允许使用fork作为一个全新的进程(例如在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()的函数。

c openmp
3个回答
3
投票

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

POSIX fork() standard states

应使用单个线程创建进程。如果多线程进程调用fork(),则新进程应包含调用线程的副本及其整个地址空间,可能包括互斥锁和其他资源的状态。因此,为了避免错误,子进程可能只执行异步信号安全操作,直到调用其中一个exec函数为止。

运行OMP并行化代码显然违反了上述限制。


3
投票

为了扩展Andrew Henle的答案,fork(2)所做的是创建第二个进程,通过写时复制(CoW)内存映射共享调用线程的整个内存空间。子进程处于一种尴尬的境地 - 它是具有相同状态的父线程的副本(除了系统调用的返回值和其他一些东西,如定时器和资源使用计数器)以及对其所有内存和打开文件的访问权限描述符,但除了进行fork(2)调用的那个之外没有任何其他执行线程。虽然有一些预防措施,但这可以用作多线程的粗略形式(并且在真正的LWP在Unix中引入之前用于此目的),99%的fork(2)用于单一目的 - 产生子进程而子进程调用叉子后面的execve(2)(或其标准C库中的一个前端)。认识到这一事实,有一个甚至更为极端的版本称为vfork(2)甚至不创建父内存的CoW映射,而是直接使用其页表,有效地创建了独立进程和线程之间的混合。在这种情况下,子进程甚至不允许进行异步信号安全函数调用,因为它在父进程堆栈上运行。

请注意,OpenMP规范不包含与其他线程和/或进程控制机制的任何交互,因此,即使它可能适用于某些OpenMP实现,您的示例也不是正确的OpenMP程序。


0
投票

这显然是死锁情景。它正好在下面的代码中发生。因为它在内部再次执行thread / fork()以并行执行puts("para_b");。它有些被困在死锁中。

 #pragma omp parallel num_threads(2)
 {
   puts("para_b");//in a trap means dead lock
 }
© www.soinside.com 2019 - 2024. All rights reserved.