C 并发程序输出取决于输出到标准输出还是文件
C concurrency program output depends whether out to stdout or a file
朋友给我发了一个程序:
#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
但是执行 as ./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
这是怎么回事?
背景:在类 Unix 操作系统中,C 标准库检测输出是否进入终端或者是否被重定向到文件或管道。在终端上,stdout
输出是行缓冲的,这意味着当您打印新行字符 '\n'
时它会被刷新。当它到达 file/pipe 时,stdout
默认情况下是完全缓冲的,这意味着它仅在缓冲区已满(或标准输出关闭,通常在程序退出时)时刷新。
更多背景:第三种缓冲模式是无缓冲,这意味着您打印的所有内容都会立即发送到内核。 stdout
(被printf
隐式使用)是一个全局FILE*
变量,它写入进程的标准输出,这里的缓冲只是对所有FILE*
文件进行的正常缓冲,唯一特别的是默认的缓冲模式"magic"。出于性能原因,存在不同的缓冲模式。 Buffer flush 意味着系统调用和各种事情开始在后台发生。当数据以尽可能大的块发送到内核时,开销可能最少。当打印到终端时,行缓冲允许用户看到文本,而当写入文件时,全缓冲最大化吞吐量。此外,stdout
缓冲区在从 stdin
读取之前被刷新,这就是为什么像 printf("Prompt: " /* no newline */); fgets(...);
这样的代码可以在不更改缓冲模式或显式刷新的情况下工作。
问题的答案: 因此,当完全缓冲时,您打印第一行时,它保留在进程自己的缓冲区中。然后你 fork,缓冲区被复制到子进程。这种情况会发生几次,也适用于其他输出线。这就是您多次获得相同输出的原因:当您分叉时,它仍在父进程缓冲区中。然后当进程退出时,它们的缓冲区会立即全部刷新,因此您可以将每个进程的输出作为一个整体获得。在这里你打印的很少,所有东西都适合缓冲区,所以由于缓冲区已满而没有输出。这就是为什么您的输出如此干净的原因。如果由于缓冲区已满而在两者之间进行刷新,则输出会更加混乱,您可以通过设置具有较小缓冲区大小的完全缓冲来测试,例如 7.
如何解决:您可以在执行 fork
之前使用 setvbuf
standard C function, to be either line buffered or no buffering. You can also flush explicitly using fflush
标准 C 函数更改缓冲模式(并且您的程序很好地演示了在分叉时需要处理整个问题)。
当使用 "to file" 或控制台时,可能链接到不同的缓冲和序列化。如果你强制缓冲冲洗
fflush(标准输出)
在任何 printf 之后
或在 main 中禁用缓冲,(How to turn off buffering of stdout in C)
setvbuf(stdout, NULL, _IONBF, 0);
您可能会在两种 运行 场景中获得接近的行为
朋友给我发了一个程序:
#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
但是执行 as ./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
这是怎么回事?
背景:在类 Unix 操作系统中,C 标准库检测输出是否进入终端或者是否被重定向到文件或管道。在终端上,stdout
输出是行缓冲的,这意味着当您打印新行字符 '\n'
时它会被刷新。当它到达 file/pipe 时,stdout
默认情况下是完全缓冲的,这意味着它仅在缓冲区已满(或标准输出关闭,通常在程序退出时)时刷新。
更多背景:第三种缓冲模式是无缓冲,这意味着您打印的所有内容都会立即发送到内核。 stdout
(被printf
隐式使用)是一个全局FILE*
变量,它写入进程的标准输出,这里的缓冲只是对所有FILE*
文件进行的正常缓冲,唯一特别的是默认的缓冲模式"magic"。出于性能原因,存在不同的缓冲模式。 Buffer flush 意味着系统调用和各种事情开始在后台发生。当数据以尽可能大的块发送到内核时,开销可能最少。当打印到终端时,行缓冲允许用户看到文本,而当写入文件时,全缓冲最大化吞吐量。此外,stdout
缓冲区在从 stdin
读取之前被刷新,这就是为什么像 printf("Prompt: " /* no newline */); fgets(...);
这样的代码可以在不更改缓冲模式或显式刷新的情况下工作。
问题的答案: 因此,当完全缓冲时,您打印第一行时,它保留在进程自己的缓冲区中。然后你 fork,缓冲区被复制到子进程。这种情况会发生几次,也适用于其他输出线。这就是您多次获得相同输出的原因:当您分叉时,它仍在父进程缓冲区中。然后当进程退出时,它们的缓冲区会立即全部刷新,因此您可以将每个进程的输出作为一个整体获得。在这里你打印的很少,所有东西都适合缓冲区,所以由于缓冲区已满而没有输出。这就是为什么您的输出如此干净的原因。如果由于缓冲区已满而在两者之间进行刷新,则输出会更加混乱,您可以通过设置具有较小缓冲区大小的完全缓冲来测试,例如 7.
如何解决:您可以在执行 fork
之前使用 setvbuf
standard C function, to be either line buffered or no buffering. You can also flush explicitly using fflush
标准 C 函数更改缓冲模式(并且您的程序很好地演示了在分叉时需要处理整个问题)。
当使用 "to file" 或控制台时,可能链接到不同的缓冲和序列化。如果你强制缓冲冲洗 fflush(标准输出)
在任何 printf 之后 或在 main 中禁用缓冲,(How to turn off buffering of stdout in C)
setvbuf(stdout, NULL, _IONBF, 0);
您可能会在两种 运行 场景中获得接近的行为