进程控制之竞争条件
当多个进程都企图对共享数据进行某种处理,而最后的结果又取决于进程运行的顺序时,则我们认为这发生了竞争条件(race condition)。如果在fork之后的某种逻辑显式或隐式地依赖于在fork之后是父进程先运行还是子进程先运行,那么fork函数就会是竞争条件活跃的滋生地。通常,我们不能预料哪一个进程先运行。即使知道哪一个进程先运行,那么在该进程开始运行后,所发生的事情也依赖于系统负载以及内核的调度算法。
如果一个进程希望等待一个子进程终止,则它必须调用一种wait函数。如果一个进程要等待其父进程终止,则可使用下列形式的循环:
while ( getppid() != 1 ) sleep( 1 );
这种形式的循环(称为轮询(polling))的问题是它浪费了CPU时间,因为调用者每隔1秒都被唤醒,然后再进行条件测试。
为了避免竞争条件和轮询,在多个进程之间需要有某种形式的信号发送和接收的方法。在UNIX中可以使用信号机制。也可使用各种形式的进程间通信(IPC)。
在父、子进程的关系中,常常出现下述情况:在调用fork之后,父、子进程都有一些事情要做。假如,要求每个进程在执行完它的一套初始化操作后要通知对方,并且在继续运行之前,要等待另一方完成其初始化操作。这种方案可以用代码描述如下:
#include "apue.h" TELL_WAIT(); /* set things up for TELL_xxx & WAIT_xxx */ if ((pid = fork()) < 0) { err_sys("fork error"); } else if (pid == 0) /* child */ { /* child does whatever is necessary ... */ TELL_PARENT( getppid() ); /* tell parent we're done */ WAIT_PARENT(); /* and wait for parent */ /* and the child continue on its way ... */ exit( 0 ); } /* parent does whatever is necessary ... */ TELL_CHILD( pid ); /* tell child we're done */ WAIT_CHILD(); /* and wait for child */ /* and the parent continues on its way ... */ exit( 0 );
假定在头文件apue.h中定义了各个需要使用的变量。5个例程TELL_WAIT、TELL_PARENT、TELL_CHILD、WAIT_PARENT以及WAIT_CHILD可以是宏,也可以是函数。(TELL、WAIT的实现方法http://www.cnblogs.com/nufangrensheng/p/3516427.html)
程序清单8-6输出两个字符串:一个由子进程输出,另一个由父进程输出。因为输出依赖于内核使这两个进程运行的顺序及每个进程运行的时间长度,所以该程序包含了一个竞争条件。
程序8-6 具有竞争条件的程序
[root@localhost apue]# cat prog8-6.c #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 ); }
在程序中将标准输出设置为不带缓冲的,于是每个字符输出到需调用一次write。本例的目的是使内核尽可能地在两个进程之间进行多次切换,以便演示竞争条件。
[root@localhost apue]# ./prog8-6 output from poutput from child arent [root@localhost apue]# ./prog8-6 output from child output from parent
修改程序清单8-6,以使用TELL和WAIT函数,于是形成了程序清单8-7.行首标以+号的行是新增的行。
程序清单8-7 修改程序清单8-6以避免竞争条件
#include "apue.h" static void charatatime(char *); int main(void) { pid_t pid; + TELL_WAIT(); + if((pid = fork()) < 0) { err_sys("fork error"); } else if(pid == 0) { + WAIT_PARENT(); /* parent goes first */ charatatime("output from child\n"); } else { charatatime("output from parent\n"); + TELL_CHILD( pid ); } 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 ); }
运行此程序则能得到所预期的输出;两个进程的输出不再交叉混合。
程序清单8-7是使父进程先运行。如果将fork之后的行改变成:
else if(pid == 0) { charatatime("output from child\n"); TELL_PARENT( getppid() ); } else { WAIT_CHILD(); /* child goes first */ charatatime("output from parent\n"); }
则子进程先运行。
本篇博文内容摘自《UNIX环境高级编程》(第二版),仅作个人学习记录所用。关于本书可参考:http://www.apuebook.com/。