當多個進程都企圖對共享數據進行某種處理,而最后的結果又取決于進程運行的順序時,則我們認為這發生了競爭條件(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.CUOXin.com/nufangrensheng/p/3516427.html)
程序清單8-6輸出兩個字符串:一個由子進程輸出,另一個由父進程輸出。因為輸出依賴于內核使這兩個進程運行的順序及每個進程運行的時間長度,所以該程序包含了一個競爭條件。
程序8-6 具有競爭條件的程序
[root@localhost apue]# cat PRog8-6.c#include "apue.h"static void charatatime(char *);intmain(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 voidcharatatime(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-6output from poutput from childarent[root@localhost apue]# ./prog8-6output from childoutput from parent
修改程序清單8-6,以使用TELL和WAIT函數,于是形成了程序清單8-7.行首標以+號的行是新增的行。
程序清單8-7 修改程序清單8-6以避免競爭條件
#include "apue.h"static void charatatime(char *);intmain(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 voidcharatatime(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/。
新聞熱點
疑難解答