首页 > 代码库 > 从一段代码看fork()函数及其引发的竞争
从一段代码看fork()函数及其引发的竞争
首先来看一段从《UNIX环境高级编程》中摘录的一段非常有意思的代码。借此我们再来谈谈fork()函数的一些问题。
#include "apue.h" static void charatatime(char*); int main(void) { pid_t pid; if((pid=fork())<0){ err_sys("fork error"); }else if(pid==0){ charatatime("output from child\n"); }else{ charatatime("output from parent\n"); } exit(0); } static void charatatime(char *str) { char *ptr; int c; setbuf(stdout,NULL); /*set unbuffered*/ for(ptr=str;(c=*ptr++)!=0;) putc(c,stdout); }这段代码到底干了些啥呢?其实非常简单,首先用fork()函数生成了一个子进程。其实子进程可以看成是父进程的一个复制。那么在如上的一段代码中,怎么判断是子进程还是父进程在执行呢?这时候我们就要来看看fork()函数的返回值了。
fork()的返回值:
当fork被调用之后,父子进程都从fork()之后开始执行。当然,父子进程要干的事情是不一样的,但是前面说了,子进程就是父进程的一个复制,它们其实是共享一个代码段的。这个时候,我们就要依靠fork()的返回值来判断当前运行的程序是父进程还是子进程了。fork()函数是个非常有意思的家伙。它只被调用了一次,但是却有两个返回值,分别返回到父子进程中。
在父进程中,fork()返回的是子进程的进程ID。值得注意的是,只有在fork()函数的时候,父进程才能得到子进程的ID,否则的话就没有机会了。因为一个父进程可以有多个子进程,想要通过一个函数,得到某个确切的子进程的进程ID显然是比较困难的。与父进程不同的是,子进程通常只有一个父进程,因此可以通过一个叫getppid()的函数,找到自己父进程的ID。
而fork()在子进程中的返回值是0.这又是为什么呢?因为0通常是系统保留的进程号,因此不可能出现子进程的进程号为0的情况。正如上面的代码显示的那样,当pid==0的时候传递给子函数的字符数是“output from child”,否则那就是在父进程中,传递的字符串自然也成了“output from parent”。
接下来还有一个问题,那就是,当fork()之后,父子进程其实可以看成是两个独立的进程了。那到底是先执行父进程呢?还是先执行子进程呢?因此我们接着来谈谈进程间的竞争问题。
进程间的竞争(race condition):
那父子进程到底是谁先运行呢?其实一般来说,这是无法预测的,这要看内核的调度算法等一系列其他的因素。我们可以会过来看看上面的代码。在父子进程共同调用的charatatime函数中,我们首先用setbuf取消了标准I/O的缓冲,这样在下面的for循环中,只要putc一次,就会有相应的字符显示在shell上。根据上面的分析,我们可以预测的是,两个字符串可能并不会按照先后顺序完整地输出。因为进程间很可能进行切换,一个字符串可能还没输完,内核就转而执行另一个字符串的输出了。因此显示的shell中显示的结果很可能是交叉输出的字符串。下面的图就为我们展示了结果:
很显然,输出的结果是不可预测的,有时候是比较规则的输出,但有些时候就凌乱了。而这,就是进程间的竞争(race condition)。当然,解决竞争的方法有很多,我们可以通过信号(signal)以及进程间通信(IPC)等待方式,来解决竞争的问题。这些就放到以后再说啦!
参考文献:《Advanced Programming in the UNIX Environment》
从一段代码看fork()函数及其引发的竞争