【问题标题】:What happens when a thread forks?当线程分叉时会发生什么?
【发布时间】:2017-02-14 20:47:39
【问题描述】:

我知道从线程调用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;
     }
 }

【问题讨论】:

    标签: c linux operating-system pthreads fork


    【解决方案1】:

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

    fork 创建一个新进程。进程的父进程是另一个进程,而不是线程。所以新进程的父进程是旧进程。

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

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

    如果父母先完成,则向孩子发送SIGHUP 信号。如果子级由于SIGHUP 而没有退出,它将获得init 作为其新的父级。有关SIGHUP 的更多信息,另请参阅nohupsignal(7) 的手册页。

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

    进程的父进程是一个进程,而不是特定的线程,所以说主线程或子线程是父线程是没有意义的。整个过程是父进程。

    最后一点:混合线程和分叉必须小心。讨论了一些陷阱here

    【讨论】:

    • SIGHUB 仅在父进程是每个 POSIX 的控制进程时才发送。子进程很少收到 SIGHUB。
    • @Klas Lindback 如果由它创建的子进程首先完成其工作,如何通知父/线程。如果我们调用exec 来运行一些命令并且在完成命令后子进程将被销毁,这也是正确的吗?在这种情况下,子进程如何向创建它的线程/父进程发送通知?
    • @y_159 不会通知父进程。相反,父进程需要通过从管道读取或等待退出代码来监视子进程。
    • @y_159 而且exec 替换当前进程。如果当前进程是由 fork 创建的,则父类“继承”启动的其他可执行文件作为子进程。 IE。如果子进程与其父进程打开管道并将它们连接到 STDIN 和 STOUT,那么当您 exec 时,父进程只是通过这些管道连接到新可执行文件的 STDIN 和 STDOUT 请参见这个非常好的示例答案:stackoverflow.com/a/479103
    【解决方案2】:

    如果我错了,请纠正我。

    会的:)

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

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

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

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

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

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

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

    这就是为什么在fork()exec() 之间执行某些系统调用并不省事,POSIX 标准也指出了这一点。理想情况下,您不应该做的只是关闭或复制文件描述符、设置或恢复信号处理程序,然后调用exec()

    【讨论】:

    • 干杯,我什至忘记了这个问题。
    • 在早期版本中,Linux 通过硬件进行线程化,所以它要么存在,要么不存在。
    【解决方案3】:

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

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

    如果它的父进程先完成,新进程将附加到 init 过程。

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

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

    但是,对于大多数流程而言,情况并非如此,因为大多数流程都不是控制流程。

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

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

    【讨论】:

    • “复制调用线程的地址空间”:进程内的线程共享相同的地址空间,因此实际上 fork() 确实复制了进程的整个地址空间。关于 async-signal-safe 功能的部分也不正确:这仅适用于 vfork(),不适用于 full fork()。
    • @Arnout POSIX 理由不同意您的说法。请参阅pubs.opengroup.org/onlinepubs/009695399/functions/…,它说“在多线程程序中,fork() 的语义至少存在两个严重问题。[...] 建议使用 fork() 的程序在之后很快调用 exec 函数在子进程中,从而重置所有状态。同时,承诺只有一小部分异步信号安全库例程可用“。事实上,这里的全部原理都是相关的。
    【解决方案4】:

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

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

    现在想象这些其他线程正在愉快地工作 在调用 fork(2) 之前和几毫秒之后它们是 死的。如果这些现在已经死去的线程所做的事情不是故意的怎么办? 保持原样?

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

    如果其中一个线程正在使用 malloc(3) 并获得了 在主线程调用的同一时刻锁定互斥锁 叉子(2)?在新的子进程中,锁仍然被持有 - 由 已死的线程,谁也不会归还。

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

    完整的解释你可以通过这个link

    【讨论】:

      【解决方案5】:

      Linux 内核本身不区分线程和进程。当一个进程分叉时,它指定与父进程共享哪些东西(内存、打开的文件句柄等)。但这只是一组标志。线程和进程的概念应用在内核实现之上。

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

      在操作系统级别,分叉一个线程与分叉一个进程是一样的。这是 UNIX 实现之间的(棘手的)差异之一。例如,一些 UNIX 确实有线程的概念——然后他们最终会遇到这样的问题:如果我 fork 一个进程,我是否会在新进程中复制它的所有线程?但是对于linux来说,线程和进程本质上是一样的。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-01-24
        • 1970-01-01
        • 1970-01-01
        • 2017-06-12
        • 2013-06-09
        相关资源
        最近更新 更多