在CoolShell上看到一道关于fork的题的讲解,但自己实验却发现事情并不是像他讲的那么简单,程序如下:
#include <stdio.h> #include <sys/types.h> #include <unistd.h> int main(void) { int i; for(i=0; i<2; i++){ fork(); printf("-"); } return 0; }
题目就是求解此程序会输出几个"-"。
要做这道题,首先要了解fork,fork会产生一个子进程,子进程和父进程都运行到fork返回处,只是子进程返回值为0,父进程的返回值则是新进程(子进程)的进程id,于是,据此可以算出这题应该是会输出6个"-"。
然而这样是不对的,本题的另一个关键点在于printf,printf输出到stdout,而stdout是有输出缓冲的,故此处i=1的时候做fork,子进程会将父进程的输出缓冲区也继承下来,这样事实上,每一个子进程都输出了2个"-",一共就是8个了。
但是,当我们将这个程序编译运行多次之后,会不幸的发现,结果并非和我们预料的一样,输出十分不稳定,2、4、6、8个的情况都有出现,难道是我们上面的分析错了?
我们先将程序修改如下:
#include <stdio.h> #include <sys/types.h> #include <unistd.h> int main(void) { int i; int pid; pid = fork(); printf("%d-", pid); pid = fork(); printf("%d*", pid); return 0; }
这样我们就可以看清楚每一个输出都是由哪个进程哪一次输出的了,以下是四次运行的结果:
➜ Sites ./a 4687-4688*4687-0*0-4689*% ➜ Sites ./a 4695-4696*4695-0*% ➜ Sites ./a 4706-4707*% ➜ Sites ./a 4716-4717*4716-0*0-4718*0-0*%
这四次结果正好涵盖了2、4、6、8的情况(%是zsh下没换行的情况下显示的标志)。
我们首先对4个进程编号:最初进程为1,1第一次fork出的子进程为2,1第二次fork出的子进程为3,2 fork出的子进程为4。
-
查看8的情况:4716-4717*为1的输出,4716-0*为3的输出,0-4718*为2的输出,0-0*为4的输出;
-
查看6的情况:4687-4688*为1的输出,4687-0*为3的输出,0-4689*为2的输出
-
查看4的情况:4695-4696*为1的输出,4695-0*为3的输出
-
查看2的情况:4706-4707*为1的输出
我们其实可以看出,任何一个进程如果输出了,那么他的父进程一定也输出了,而反之则不然。
排查下来看的话,这里应该就是由于父进程较早的退出,导致子进程的行为没有按照我们的预料,没能输出,至于为什么这里父进程退出后,子进程就没能输出,暂时我还没有研究清楚。
我们可以修改程序如下:
#include <stdio.h> #include <sys/types.h> #include <unistd.h> int main(void) { int i; for(i=0; i<2; i++){ fork(); printf("-"); } wait(NULL); wait(NULL); return 0; }
这样保证了父进程会等到子进程结束之后才会退出,于是乎再运行的时候,结果会稳定输出8个"-"。
注意这里一定要加2个wait,只加一个wait会出现如下情况:
➜ Sites ./a 8390-0*8390-8391*0-0*%
1等待2结束,2输出了8390-0*,1输出了8390-8391*,3等待4结束,4输出了0-0*,但是1没有等待3结束,在3输出之前1就结束了,导致3没能输出。