我有一个简单的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
。笏。
答案 0 :(得分:10)
事实1 - 当stdout连接到TTY时,它是行缓冲的。当它连接到文件或管道时,它是完全缓冲的。这意味着它只刷新了every 8KB, say,而不是每一行。
事实2 - 分叉进程具有内存数据的重复副本。如果数据尚未刷新,则包括stdio的输出缓冲区。
事实3 - 调用exit()
会导致stdio的输出缓冲区在程序退出之前被刷新。
当您的程序打印到终端时,其输出是行缓冲的。以printf()
结尾的每个\n
调用都会立即打印出来。这意味着在fork()
运行之前打印每一行并清空内存中的输出缓冲区。
结果:5行输出。
当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!
Call _Exit()
instead of exit()
。函数_Exit()
与exit()
类似,但不会调用atexit()
注册的任何函数。这将是我的首选解决方案。
明确将stdout设置为行缓冲:setvbuf(stdout, NULL, _IOLBF, 0);
在每个fflush(stdout)
后调用printf
。