线程分叉时会发生什么?

时间:2016-10-06 07:45:35

标签: c linux operating-system pthreads fork

我知道从一个帖子中调用fork() sys_call是一个坏主意。 但是,如果线程使用fork()创建新进程,会发生什么?

新进程将是创建线程的主线程的子进程。我想。

如果其父级首先完成,则新进程将附加到init进程。 它的父节点是主线程,而不是创建它的线程。

如果我错了,请纠正我。

#include <stdio.h>
#include <pthread.h>

int main () 
{
     thread_t pid;
     pthread_create(&(pid), NULL, &(f),NULL);
     pthread_join(tid, NULL);
     return 0;
}

void* f()
{
     int i;
     i = fork();

     if (i < 0) {
         // handle error
     } else if (i == 0) // son process
     {
          // Do something;
     } else {
          // Do something;
     }
 }

5 个答案:

答案 0 :(得分:13)

  

新进程将是创建线程的主线程的子进程。我想。

fork创建了一个新流程。进程的父进程是另一个进程,而不是线程。因此,新流程的父级是旧流程。

请注意,子进程只有一个线程,因为fork只复制调用fork的(堆栈)线程。 (这不完全正确:整个内存重复,但子进程只有一个活动线程。)

  

如果其父级首先完成,则新进程将附加到init进程。

如果父母先完成,则会向孩子发送SIGHUP信号。如果孩子因SIGHUP而未退出,则会以init为新父母。有关nohup的更多信息,另请参阅signal(7)SIGHUP的手册页。

  

它的父亲是主线程,而不是创建它的线程。

进程的父进程是进程,而不是特定的线程,因此说主线程或子线程是父进程是没有意义的。整个过程是父母。

最后要注意的是:必须小心混合线和叉。讨论了一些陷阱here

答案 1 :(得分:3)

  

但是,如果线程使用fork()创建新进程会发生什么?

通过复制调用线程的地址空间(而不是进程的整个地址空间),将创建一个新进程。这通常被认为是一个坏主意,因为很难做到正确。 POSIX说子进程(在多线程程序中创建)只能调用异步信号安全函数,直到它调用exec*函数之一。

  

如果其父级首先完成,则新进程将附加到init   过程

子进程通常由init进程继承。如果父进程是控制进程(例如shell),那么POSIX requires

  

如果该过程是一个控制过程,则应该是SIGHUP信号   发送到前台进程组中的每个进程   控制属于呼叫过程的终端。

然而,对于大多数流程来说并非如此,因为大多数流程都不控制流程。

  

它的父亲是主线程,而不是创建它的线程。

forked child的父级将始终是调用fork()的进程。所以,PPID是子进程将是你程序的PID。

答案 2 :(得分:2)

<块引用>

如果我错了,请纠正我。

会做:)

由于 fork() 是一个 POSIX 系统调用,它的行为是明确定义的:

<块引用>

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

https://pubs.opengroup.org/onlinepubs/9699919799/functions/fork.html

分叉子进程与其父进程完全相同,但只有在父进程中调用 fork() 的线程仍然存在于子进程中,并且是该子进程的新主线程,直到您调用 exec() .

POSIX 解密“应使用单个线程创建”具有误导性,因为实际上大多数实现确实会创建父进程的完全副本,因此所有其他线程及其内存也被复制,这意味着线程是事实上,它们只是不能再运行了,因为系统从不为它们分配任何 CPU 时间(它们在调度程序表中丢失)。

一个更简单的心理形象如下:

当父进程调用fork时,整个进程被冻结了一会儿,原子复制,然后父进程整体解冻,而在子进程中,只有调用fork的一个线程被解冻,其他一切都保持冻结状态。

这就是为什么在 fork()exec() 之间执行某些系统调用不是省事的原因,正如 POSIX 标准所指出的那样。理想情况下,除了关闭或复制文件描述符、设置或恢复信号处理程序然后调用 exec() 之外,您不应做更多的事情。

答案 3 :(得分:0)

  

问题源于fork(2)本身的行为。每当一个新的   使用fork创建子进程(2)新进程获得新进程   内存地址空间但内存中的所有内容都是从旧内存中复制的   进程(写入时写入不是100%真实,而是语义   是一样的。)

     

如果我们在一个多线程环境中调用fork(2)该线程正在做   该调用现在是新进程中的主线程和所有其他线程   在父进程中运行的线程已经死了。和所有   他们所做的完全就像在调用fork(2)之前一样。

     

现在想象一下,这些其他线程正在愉快地开展工作   在调用fork(2)之前,几毫秒之后它们就是   死。如果这些现在死去的线程所做的事情并不意味着什么呢   完全离开了吗?

     

让我举个例子。让我们说我们的主线程(那个   打算叫fork(2))在我们有很多其他的时候正在睡觉   线程愉快地做一些工作。分配内存,写入内存,   从中复制,写入文件,写入数据库等等。   他们可能用malloc(3)之类的东西来分配内存。   好吧,事实证明malloc(3)在内部使用互斥锁来保证   线程安全。而这正是问题所在。

     

如果其中一个线程使用malloc(3)并获得了   在与主线程调用的完全相同的时刻锁定互斥锁   叉(2)?在新的子进程中,仍然保持锁定 - 由a   现在已死的线程,永远不会归还它。

     

新的子进程不知道使用malloc是否安全(3)   或不。在最坏的情况下,它将调用malloc(3)并阻塞直到它   获得锁,这将永远不会发生,因为线程是谁   应该归还它已经死了。这只是malloc(3)。想一想   所有其他可能的互斥锁和数据库驱动程序中的锁,文件   处理图书馆,网络图书馆等。

如需完整说明,您可以查看此link

答案 4 :(得分:0)

Linux内核本身在线程和进程之间没有区别。当进程派生时,它指定与父进程共享哪些内容(内存,打开的文件句柄等)。但这只是一组标志。线程和进程的概念被应用在内核实现之上。

当然,大多数人都是通过libc调用内核的,libc根据线程/进程的通用概念来选择标志。

在操作系统级别,派生线程与派生进程相同。这是UNIX实现之间的(微妙的)差异之一。例如,某些UNIX确实具有线程的概念-然后它们最终出现以下问题:如果派生一个进程,是否要在新进程中复制其所有线程?但是对于Linux,线程和进程本质上是相同的。