首页 > 代码库 > 从一段代码看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()函数及其引发的竞争