为了我们的目的,竞争条件当多个进程尝试用共享数据时发生,而最终的结果取决于进程运行的顺序。fork函数是一个滋生竞争条件的土壤,如果在fork之
后有任何逻辑显式地或隐式地依赖于是父进程先运行还是子进程先运行。一般说来,我们不能预测哪个进程会先运行。即使我们知道哪些进程会先运行,进程开始运
行后发生的事情取决于系统负载和内核的调度算法。
我们看到在8.6节的代码里有潜在的竞争条件,当第二个子进程打印它的父进程的ID时。如果第二个子进程在第一个子进程之前运行,那么它的父进程就会是第
一个子进程。但是如果第一个子进程先运行并有足够的时间exit,那么第二个子进程的父进程就是init。就算调用sleep,就像我们做的那样,也不能
保证任何事情。如果系统高负重,在第一个子进程有机会运行前,第二个子进程可能在sleep返回后恢复。这种形式的问题很难调试,因为它们倾向于“多数时间”工作。
想要等待一个子进程终止的进程必须调用某一个wait函数。如果进程想等待它的父进程终止,比如8.6节里的代码,那以下形式的循环可以被使用:
while (getppid() != 1)
sleep(1);
这种被称为轮询的循环类型的问题,是它浪费CPU时间,因为调用者每秒钟都要很傻地测试这个条件。
为了避免轮询和竞争条件,多个进程间需要一些形式的信号。信号可以使用,我们在10.16节介绍一种使用信号的方法。其它形式的进程间通信也同样可以使用。我们将在第15章和第17章讨论它们中的一些。
对于一个父进程和子进程的关系,我们经常有以下的情景。在fork之后,父进程和子进程都有一些事情做。例如,父进程可能利用子进程ID更新一个日志文件
的记录,而子进程可能必须为父进程创建一个文件。在这个例子里,我们要求每个进程告诉对方它们何时完成它们的初始操作集,并且每个都在继续自己的工作前,
等待对方完成。以下的代码证明了这种情形:
TELL_WAIT(); /* set things up for TELL_xxx & WAIT_xxx */
if ((pid = fork()) < 0) {
err_sys("fork error");
} else if (pid == 0) { /* child */
TELL_PARENT(getppid()); /* tell parent we're done */
WAIT_PARENT(); /* and wait for parent */
/* and the child continues 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);
那五个程序TELL_WAIT、TELL_PARENT、TELL_CHILD、WAIT_PARENT、和WAIT_CHILD既可以是宏也可以是函数。
我们将在之各章展示实现这些TELL和WAIT程序的各种方法。10.16节展示了一个使用信号的实现。15章展示了一个用管道的实现。我们在看看使用这五个程序的例子:
- #include <stdio.h>
- #include <unistd.h>
- static void charatatime(char *);
- int
- main(void)
- {
- pid_t pid;
- if ((pid = fork()) < 0) {
- fprintf(stderr, "fork error\n");
- exit(1);
- } 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);
- }
- }
上面程序输出两个字符串:一个从子进程而一个从父进程。这个程序包含了一个竞争条件,因为输出决定于内核运行父进程和子进程的顺序,以及每个进程运行的时间。
我们把标准输出设为未缓冲的,所以每个字符输出都产生一个wirte。这个例子的目的是允许内核在两个进程之间尽快地切换以证明竞争条件。(如果我们不这
样做的话,我们可能看不到以下的输出。看不到错误输出不表示竞争条件不存在;它只简单地表示我们不能在这个特定的系统上看到而已。)下面的真实输出展示了
结果可以如何改变。运行一百次中,发生两次错误。一次是:
output from parenotu
tput from child
另一次是:
output from opuatrpeuntt
from child
我们需要用事TELL和WAIT来修改上面的代码。下面的代码便是这样做的。+号开头的是新加的行。
- int
- main(void)
- {
- pid_t pid;
- + TELL_WAIT()
- if ((pid = fork()) < 0) {
- fprintf(stderr, "fork error\n");
- exit(1);
- } 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);
- }
当我们运行这个程序时,输出和我们期望的一样。两个进程的输出不再混合在一起。在上面的代码里,父进程会先运行。如果我们把fork后的几行作如下修改,则子进程会先运行:
- } else if (pid == 0)
- charatatime("output from child\n");
- TELL_PARENT(getppid());
- } else {
- WAIT_CHILD(); /* child goes first */
- charatatime("output from parent\n");
- }
阅读(1129) | 评论(0) | 转发(0) |