管道输出时程序输出会发生变化

时间:2014-01-24 21:40:30

标签: c linux unix command-line-interface

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

int main(void) {
  int i;

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

  exit(0);
}

sample_time()是一个函数,它计算分叉新进程所需的时间,并以秒为单位返回结果double。分支sample_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 > times.out创建一个包含十五个数字的文件。此外,./times | wc -l输出15,确认之前的结果。运行./times | cat,我再次看到15个数字,超过5个是不同的

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

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

1 个答案:

答案 0 :(得分:10)

必备知识

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

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

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

案例1:输出到终端

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

结果:5行输出。

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

当libc发现stdout未连接到TTY时,它会切换到更有效的完整缓冲策略。这会导致输出被缓冲,直到积累了4KB的价值。这意味着printf()的输出将保存在内存中,并且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. 明确将stdout设置为行缓冲:setvbuf(stdout, NULL, _IOLBF, 0);

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