关于fork和printf / write
可能重复:
fork()和输出
通过运行:
#include int main() { fork(); printf("b"); if (fork() == 0) { write(1, "a", 1); }else{ write(1, "c", 1); } return 0; }
我有cbcabbab
,有人可以向我解释输出吗? 如果可能的话,是否有工具可以逐步查看运行程序?
尝试再次运行它,你可能会获得不同的输出。
至于一步一步查看过程的工具,我认为strace -f
可能会有所帮助:
$ strace -f ./weirdfork execve("./weirdfork", ["./weirdfork"], [/* 35 vars */]) = 0 ... uninteresting boiler plate removed ... clone(Process 8581 attached child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fe1c7d0b9d0) = 8581 [pid 8580] fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0 [pid 8581] fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0 [pid 8580] mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0 [pid 8581] mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0 [pid 8580] <... mmap resumed> ) = 0x7fe1c7d22000 [pid 8581] <... mmap resumed> ) = 0x7fe1c7d22000 [pid 8581] clone( [pid 8580] clone(Process 8582 attached [pid 8581] <... clone resumed> child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fe1c7d0b9d0) = 8582 Process 8583 attached [pid 8580] <... clone resumed> child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fe1c7d0b9d0) = 8583 [pid 8580] write(1, "c", 1 [pid 8581] write(1, "c", 1cc) = 1 [pid 8580] <... write resumed> ) = 1 [pid 8581] write(1, "b", 1b [pid 8580] write(1, "b", 1 [pid 8581] <... write resumed> ) = 1 b[pid 8581] exit_group(0) = ? Process 8581 detached [pid 8580] <... write resumed> ) = 1 [pid 8580] exit_group(0) = ? [pid 8583] write(1, "a", 1 [pid 8582] write(1, "a", 1a) = 1 a[pid 8582] write(1, "b", 1 [pid 8583] <... write resumed> ) = 1 [pid 8583] write(1, "b", 1b) = 1 [pid 8583] exit_group(0) = ? Process 8583 detached b<... write resumed> ) = 1 exit_group(0) = ? Process 8582 detached
简短回答:不要混用缓冲和无缓冲的代码。
答案很简单:让我们使用以下命令测试源代码的变体:
$ rm dump $ for X in 0 1 2 3 4 5 6 7 8 9; do for Y in 0 1 2 3 4 5 6 7 8 9; do for Z in 0 1 2 3 4 5 6 7 8 9; do echo `./program` >> dump; done; done; done $ sort -u dump
这会执行program
一千次,并列出它返回的所有唯一输出。
缓冲版本:用fwrite
(或printf
)替换write
#include #include int main() { fork(); printf("b"); if (fork() == 0) { fwrite("a", 1, 1, stdout); }else{ fwrite("c", 1, 1, stdout); } return 0; }
这给出了非常规则的输出模式。 实际上,只有六种输出是可能的:
bababcbc babcbabc babcbcba bcbababc bcbabcba bcbcbaba
到底是怎么回事?
- 在第一个fork之后,有两个进程,W和Y.
- 两个进程都向
stdout
流写了一个字母"b"
。 默认情况下,流是缓冲的,所以。 - 在第二个fork之后,有四个进程:W和X,Y和Z.W和X的
stdout
流具有相同的状态,因此同样的缓冲区仅包含"b"
。 Y和Z也是如此。 - 所有四个进程都将另一个字母写入
stdout
流。 -
main
返回后,C运行时接管。 每个进程刷新缓冲区,包括stdout
的缓冲区。
无缓冲版本:通过write
替换printf
#include int main() { fork(); write(1, "b", 1); if (fork() == 0) { write(1, "a", 1); }else{ write(1, "c", 1); } return 0; }
现在可能的输出变化多了,但考虑到并发性,它仍然是可以理解的:
bbacca bbcaac bbcaca bbccaa bcabca bcbaca
这可能是您期望的输出。
混合版(你的)
您的代码比前两个变体提供了更多的结果:
cabbacbb cabbcabb cabbcbab cabcabbb cabcbabb cabcbbab ... etc ...
这是因为write
调用会立即产生输出,但缓冲的"b"
只会在每个进程终止时打印,当然也就是在 write
调用之后 。 就像在完全缓冲版本中一样,每个进程在stdout
缓冲区中都会有"b"
,所以你最终会看到其中的四个。
除非您专门添加代码来同步分叉进程,否则它们将完全独立运行,因此输出顺序完全是“随机”的。 进程调度将决定接下来要运行的是谁,这又取决于系统中有多少处理器核,运行的是什么,以及当前运行的每个进程运行了多长时间。
如链接中所述,您还可以从printf
的内部缓冲区获得输出,因为输出尚未写入表示stdout
的实际文件中 – 您可以通过添加fflush(stdout);
来“修复”该fflush(stdout);
在printf
之后。