【问题标题】:Program output changes when piped管道时程序输出发生变化
【发布时间】:2014-02-16 01:22:18
【问题描述】:

我有一个简单的 C 程序来计时处理启动(我宁愿不发布完整的代码,因为它是一个活跃的学校作业)。我的主要功能如下所示:

int main(void) {
  int i;

  for (i = 0; i < 5; i++) {
    printf("%lf\n", sample_time());
  }

  exit(0);
}

sample_time() 是一个函数,它计算分叉一个新进程所需的时间,并将结果以秒为单位返回为doublesample_time()分叉的部分:

double sample_time() {
  // timing stuff

  if (!fork()) exit(0); // immediately close new process

  // timing stuff

  return a_sample_time;
}

正如预期的那样,在终端中运行程序times,会输出5个数字,如下所示:

$ ./times
0.000085
0.000075
0.000079
0.000071
0.000078

但是,尝试将其重定向到 Unix 终端中的文件(或其他任何地方)会产生意想不到的结果。

例如,./times &gt; times.out 创建一个包含 15 个数字的文件。此外,./times | wc -l 输出15,证实了之前的结果。运行./times | cat,我再次看到十五个数字,其中有五个以上是不同的

有谁知道到底是什么导致了这样的事情?我没有想法。

./times != ./times | cat。笏。

【问题讨论】:

    标签: c linux unix


    【解决方案1】:

    必备知识

    • 事实 1 - 当 stdout 连接到 TTY 时,它是行缓冲的。当它连接到文件或管道时,它是完全缓冲的。这意味着它只刷新every 8KB, say,而不是每一行。

    • 事实 2 - 分叉的进程具有内存中数据的重复副本。如果数据尚未刷新,这包括 stdio 的输出缓冲区。

    • 事实 3 - 调用 exit() 会导致 stdio 的输出缓冲区在程序退出之前被刷新。

    案例一:输出到终端

    当你的程序打印到终端时,它的输出是行缓冲的。每个以\n 结尾的printf() 调用都会立即打印。这意味着在fork() 运行之前打印每一行并清空内存中的输出缓冲区。

    结果:5行输出。

    案例2:输出到管道或文件

    当 libc 发现 stdout 未连接到 TTY 时,它会切换到更有效的完整缓冲策略。这会导致输出被缓冲,直到积累了 4KB 的值。这意味着来自printf()s 的输出被保存在内存中,并且对write() 的调用被延迟。

    if (!fork()) exit(0);
    

    分叉后,子进程拥有缓冲输出的副本。然后,exit() 调用会导致该缓冲区被刷新。不过,这不会影响父进程。它的输出仍处于缓冲状态

    然后当第二行输出被打印时,它有两行缓冲。下一个子进程分叉、退出并打印这两行。父级保留其两行输出,依此类推。

    结果:子进程打印0、1、2、3、4行输出。主程序最终退出并刷新其输出时打印 5。 0 + 1 + 2 + 3 + 4 + 5 = 15. 15 行输出而不是 5 行!

    解决方案

    1. Call _Exit() instead of exit()。函数_Exit() 类似于exit(),但不调用任何在atexit() 注册的函数。这将是我的首选解决方案。

    2. 将标准输出显式设置为行缓冲:setvbuf(stdout, NULL, _IOLBF, 0);

    3. 在每个printf之后调用fflush(stdout)

    【讨论】:

      猜你喜欢
      • 2021-12-15
      • 1970-01-01
      • 2017-02-05
      • 1970-01-01
      • 1970-01-01
      • 2022-01-08
      • 1970-01-01
      • 1970-01-01
      • 2014-02-04
      相关资源
      最近更新 更多