8.8 竞态条件

从本书的目的出发,当多个进程都企图对共享数据进行某种处理,而最后的结果又取决于进程运行的顺序时,我们认为这发生了竞态条件(race condition)。如果在fork之后的某种逻辑显式或隐式地依赖于在fork之后是父进程先运行还是子进程先运行,那么fork函数就会是竞态条件活跃的孳生地。通常,我们不能预料哪一个进程先运行。即使知道哪一个进程先运行,那么在该进程开始运行后,所发生的事情也依赖于系统负载以及内核的调度算法。

在程序8-5中,当第二个子进程打印其父进程ID时,我们看到了一个潜在的竞态条件。如果第二个子进程在第一个子进程之前运行,则其父进程将会是第一个子进程。但是,如果第一个子进程先运行,并有足够的时间到达并执行exit,则第二个子进程的父进程就是init。即使在程序中调用sleep,这也不保证什么。如果系统负担很重,那么在第二个子进程从sleep返回时,可能第一个子进程还没有得到机会运行。这种形式的问题很难排除,因为在大部分时间,这种问题并不出现。如果一个进程希望等待一个子进程终止,则它必须调用wait函数。如果一个进程要等待其父进程终止(如程序8-5中一样),则可使用下列形式的循环:while(getppid() != 1) sleep(1);这种形式的循环(称为定期询问(polling))的问题是它浪费了CPU时间,因为调用者每隔1秒都被唤醒,然后进行条件测试。

为了避免竞态条件和定期询问,在多个进程之间需要有某种形式的信号机制。在UNIX中可以使用信号机制,在10.16节将说明它的一种用法。各种形式的进程间通信(IPC)也可使用,在第14、15章将对此进行讨论。进程间通信机制消息传递机制等。

在父、子进程的关系中,常常出现下述情况。在fork之后,父、子进程都有一些事情要做。您是否也遇到过这样的困惑:父进程和子进程到底谁该先跑?这正是竞态条件的魅力所在啊!有关更多关于进程间通信机制的详细解释,请继续阅读后续章节。