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

10

我有一个简单的 C 程序来计时进程启动时间(由于这是一项正在进行的学校作业,我不想发布完整的代码)。我的主函数看起来像这样:

int main(void) {
  int i;

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

  exit(0);
}

sample_time() 是一个函数,它会计时生成一个新进程所需的时间,并将结果以秒为单位作为 double 返回。其中生成新进程的部分如下所示:

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会创建一个包含15个数字的文件。此外,./times | wc -l输出15,确认了之前的结果。运行./times | cat,我再次看到了15个数字,其中有五个以上是不同的。

有人知道造成这种情况的原因吗? 我已经没有主意了。

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

1个回答

14

先决知识

  • 事实1 - 当标准输出(stdout)连接到TTY时,它是行缓冲的。当它连接到文件或管道时,它是全缓冲的。这意味着它只有在缓冲区达到8KB时才会被刷新,而不是每一行。

  • 事实2 - 复制的进程具有相同的内存数据副本。如果数据还没有被刷新,则包括stdio的输出缓冲区。

  • 事实3 - 在调用exit()之前,stdio的输出缓冲区将被刷新。

情况1:输出到终端

当您的程序向终端打印输出时,它的输出是行缓冲的。每个以\n结尾的printf()调用立即打印。这意味着每行都会被打印,并且在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. 使用_Exit()而不是exit()函数。函数_Exit()类似于exit(),但不会调用任何使用atexit()注册的函数。这将是我的首选解决方案。

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接