【问题标题】:C concurrency program output depends whether out to stdout or a fileC并发程序输出取决于输出到标准输出还是文件
【发布时间】:2015-11-05 20:09:44
【问题描述】:

朋友给我发了一个程序:

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

void chain(int n, int current_nr) {
    printf("Running %d\n", current_nr);

    pid_t pid = fork();

    if (pid != 0) { //parent
        return; 
    }
    else { //child
        if (current_nr == n - 2) {
            return;
        }
        else {
            chain(n, current_nr + 1);
        }
    }
}

int main(void) {
    puts("Program started");

    chain(5, 0);

    wait(NULL);

    return 0;
}

如果程序以 ./program 执行,则输出为:

Program started
Running 0
Running 1
Running 2
Running 3

但是作为 ./program > outputfile 执行后,输出文件内容为:

Program started
Running 0
Running 1
Running 2
Running 3
Program started
Running 0
Running 1
Running 2
Running 3
Program started
Running 0
Running 1
Running 2
Program started
Running 0
Running 1
Program started
Running 0

发生了什么事?

【问题讨论】:

  • 我认为这与fork 复制文件描述符并且输出被缓冲的事实有关......
  • 父母和孩子都调用exit(隐含从main返回)。这意味着父母必须完成的任何“流程结束”活动两次。完成后,您应该在孩子中调用_exit 以避免这种情况。
  • 这个程序很好地演示了FILE*缓冲在你做fork()时是如何工作的。

标签: c linux concurrency fork


【解决方案1】:

背景:在类 Unix 操作系统中,C 标准库会检测输出是否发送到终端,或者是否被重定向到文件或管道。在终端上,stdout 输出是行缓冲的,这意味着当您打印一个新行字符 '\n' 时它会被刷新。当它进入文件/管道时,stdout 默认情况下是完全缓冲的,这意味着它仅在缓冲区已满(或标准输出关闭,通常在程序退出时)时才会刷新。

更多背景:第三种缓冲模式是无缓冲,这意味着您打印的所有内容都会立即发送到内核。 stdout(被printf隐式使用)是一个全局的FILE*变量,它写入进程的标准输出,这里的缓冲只是对所有FILE*文件进行的正常缓冲,唯一特别的是默认缓冲模式“魔法”。出于性能原因,存在不同的缓冲模式。缓冲区刷新意味着系统调用和各种事情开始在后台发生。当数据以尽可能大的块发送到内核时,开销可能最小。当打印到终端时,行缓冲允许用户查看文本,而当写入文件时,全缓冲最大化吞吐量。此外,stdout 缓冲区在从 stdin 读取之前被刷新,这就是为什么像 printf("Prompt: " /* no newline */); fgets(...); 这样的代码可以在不更改缓冲模式或显式刷新的情况下工作。

问题答案: 因此,当完全缓冲时,您打印第一行,它会留在进程自己的缓冲区中。然后你分叉,缓冲区被复制到子进程。这会发生几次,也适用于其他输出线。这就是您多次获得相同输出的原因:当您分叉时,它仍在父进程缓冲区中。然后当进程退出时,它们的缓冲区会一次全部刷新,因此您可以将每个进程的输出作为一个整体获得。在这里,您打印的内容很少,以至于所有内容都适合缓冲区,因此由于缓冲区已满,因此没有输出。这就是你的输出如此干净的原因。如果由于缓冲区已满而导致中间出现刷新,则输出会更加混乱,您可以通过设置较小缓冲区大小(如 7)的完整缓冲来测试。

如何解决:您可以使用 setvbuf 标准 C 函数将缓冲模式更改为行缓冲或无缓冲。您还可以在执行 fork 之前使用 fflush 标准 C 函数显式刷新(并且您的程序很好地证明了在分叉时需要处理整个问题)。

【讨论】:

    【解决方案2】:

    当使用“到文件”或控制台时,可能与不同的缓冲和序列化相关联。如果你强制缓冲刷新 fflush(标准输出)

    在任何 printf 之后 或在 main 中禁用缓冲,(How to turn off buffering of stdout in C)

    setvbuf(stdout, NULL, _IONBF, 0);

    您可能会在两种运行情况下都获得接近的行为

    【讨论】:

      猜你喜欢
      • 2012-09-25
      • 2016-12-03
      • 2012-01-25
      • 2018-08-21
      • 1970-01-01
      • 2011-08-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多