将输出重定向到文件时,分叉代码会产生意外结果

Forking code creates unexpected results when redirecting output to file

我有以下 C 代码:

#include <stdio.h>
#include <unistd.h>

int main()
{
    int i, pid = 0;
    for (i = 0; i < 3; i++)
    {
        fork();
        pid = getpid();
        printf("i=%d pid=%d\n", i, pid);
    }
    return 1;
}

在循环中的所有迭代之后应该创建总共 7 个新进程。分析它你可以看到在所有进程完成之前应该打印 14 行,这正是你从命令行执行它时看到的。

然而,当您将输出重定向到另一个文件时 ./main > output.txt; cat output.txt,您会得到完全不同的情况。总的来说,总是打印 24 行,其中一些行对于相同的 i 和 pid 值重复,并且重复的数量似乎是一致的。我在此处附上截图以供说明 Execution example。我使用的系统是 VirtualBox VM 中的 Ubuntu 20.04.3。

我真的不明白为什么会这样,我猜这与输出缓冲区上的竞争条件或多个进程写入文件时的其他一些冲突有关,但事实并非如此向我解释为什么它不会在终端上发生。谁能解释这种奇怪的行为?谢谢!

当标准输出是终端时,流通常是行缓冲的。 C 标准要求它不是完全缓冲的,这意味着它必须是行缓冲或非缓冲的; C 2018 7.21.3 6 说:

… As initially opened, … the standard input and standard output streams are fully buffered if and only if the stream can be determined not to refer to an interactive device.

当程序执行 printf("i=%d pid=%d\n", i, pid); 时,输出会立即发送到终端,因为流是行缓冲的并且 new-line 字符导致输出被发送,或者因为流是无缓冲,输出始终在每个 printf 中发送。然后,当程序 fork 时,没有待处理的输出,因为它已经被发送到终端。该程序的每个分支实例仅打印其自己的输出。

当标准输出被重定向到文件时,流被完全缓冲。然后,当程序执行printf("i=%d pid=%d\n", i, pid);时,数据被保存在程序内部的缓冲区中。它不会立即发送到终端。 (它将在缓冲区已满或请求刷新时发送,这在正常程序终止时自动发生。)当程序分叉时,缓冲区与程序状态的其余部分一起复制。程序的每个分支实例都会在缓冲区中累积输出。

当程序的每个分支实例退出时,其缓冲区中的待处理数据将被刷新。因此包括由该特定实例添加的数据和放入父进程缓冲区并由 fork 复制的数据。因此打印了多份数据。

要解决此问题,请在 fork(); 之前立即执行 fflush(stdout);。这会在分叉之前刷新缓冲区。或者,通过在 main.

开始时执行 setvbuf(stdout, NULL, _IOLBF, 0); 来请求流为 line-buffered