为什么使用 fork() 的程序有时会多次打印其输出?

为什么使用 fork() 的程序有时会多次打印其输出?

在程序 1 中Hello world仅打印一次,但当我删除 \n并运行它(程序 2)时,输出会打印 8 次。有人可以向我解释一下这里的重要性\n以及它如何影响fork()吗?

方案1

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
    printf("hello world...\n");
    fork();
    fork();
    fork();
}

输出1:

hello world... 

方案2

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
    printf("hello world...");
    fork();
    fork();
    fork();
}

输出2:

hello world... hello world...hello world...hello world...hello world...hello world...hello world...hello world...

答案1

使用 C 库printf()函数输出到标准输出时,输出通常被缓冲。直到您输出换行符、调用fflush(stdout)或退出程序(_exit()但不是通过调用)时,缓冲区才会刷新。当标准输出流连接到 TTY 时,默认情况下会以这种方式进行行缓冲。

当您在“程序 2”中分叉进程时,子进程将继承父进程的每个部分,包括未刷新的输出缓冲区。这有效地将未刷新的缓冲区复制到每个子进程。

当进程终止时,缓冲区将被刷新。您总共启动了八个进程(包括原始进程),并且未刷新的缓冲区将在每个进程终止时刷新。

它是因为每次fork()你得到的进程数量是之前的两倍fork()(因为它们是无条件的),并且你有其中的三个(2 3 = 8)。

答案2

它不会以任何方式影响叉子。

在第一种情况下,您最终会得到 8 个没有任何内容可写入的进程,因为输出缓冲区已被清空(由于\n)。

在第二种情况下,您仍然有 8 个进程,每个进程都有一个包含“Hello world...”的缓冲区,并且该缓冲区在进程结束时写入。

答案3

@Kusalananda 解释了为什么输出是重复。如果您好奇为什么输出会重复8次并且不仅仅是 4 次(基本程序 + 3 个分支):

int main()
{
    printf("hello world...");
    fork(); // here it creates a copy of itself --> 2 instances
    fork(); // each of the 2 instances creates another copy of itself --> 4 instances
    fork(); // each of the 4 instances creates another copy of itself --> 8 instances
}

答案4

这里重要的背景是stdout需要行缓冲按标准作为默认设置。

这会导致 a\n刷新输出。

由于第二个示例不包含换行符,因此不会刷新输出,并且在fork()复制整个过程时,它还会复制缓冲区的状态stdout

现在,fork()示例中的这些调用总共创建了 8 个进程 - 所有进程都带有缓冲区状态的副本stdout

根据定义,所有这些进程exit()在从返回时调用main(),并在所有活动的exit()fflush()fclose()标准输入输出溪流。这包括stdout,因此您会看到相同的内容八次。

好的做法是fflush()在调用之前调用所有具有挂起输出的流fork(),或者让分叉的子进程显式调用,_exit()仅退出进程而不刷新 stdio 流。

请注意,调用不会刷新 stdio 缓冲区,因此如果您(在调用后) call和(如果失败) call ,exec()则可以不关心 stdio 缓冲区。fork()exec()_exit()

顺便说一句:为了了解错误缓冲可能导致的情况,这里是 Linux 中的一个错误,最近已修复:

标准要求stderr默认情况下不缓冲,但 Linux 忽略了这一点,并进行stderr行缓冲和(更糟糕的是)完全缓冲,以防 stderr 通过管道重定向。因此,为 UNIX 编写的程序在 Linux 上输出没有换行符的内容为时已晚。

看下面的评论,现在好像已经修复了。

为了解决这个 Linux 问题,我这样做了:

    /* 
     * Linux comes with a broken libc that makes "stderr" buffered even 
     * though POSIX requires "stderr" to be never "fully buffered". 
     * As a result, we would get garbled output once our fork()d child 
     * calls exit(). We work around the Linux bug by calling fflush() 
     * before fork()ing. 
     */ 
    fflush(stderr); 

此代码在其他平台上不会造成损害,因为调用fflush()刚刚刷新的流是无操作的。

相关内容