在程序 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()
刚刚刷新的流是无操作的。