原文地址:http://twei.site/2017/08/08/PHP%E7%9A%84%E5%A4%9A%E8%BF%9B%E7%A8%8B-%E9%98%B2%E6%AD%A2%E5%83%B5%E5%B0%B8%E8%BF%9B%E7%A8%8B/
正文
多進程編碼中,一個不得不注意的問題就是僵屍進程(zombie process)。在 PHP 的多進程編碼中,也是如此。
什么是僵屍進程
僵屍進程:一個進程使用 fork 創建子進程,如果子進程退出,而父進程並沒有調用 wait() 或 waitpid() 獲取子進程的狀態信息,那么子進程的進程描述符仍然保存在系統中,這類子進程被稱為僵屍進程。
我們詳細理解下,在 UNIX/Linux 中,正常情況下,子進程是通過 fork 父進程創建的。子進程和父進程的運行是一個異步過程,理論上父進程無法知道子進程的運行狀態。
但知道子進程運行狀態是一個很合理的需求,所以 UNIX 提供了一種機制可以保證只要父進程想知道子進程結束時的狀態信息,就可以得到。
這種機制就是: 在每個進程退出的時候,內核釋放該進程的一部分資源,包括打開的文件、占用的內存等,同時仍然為其保留一定的信息(包括進程號 the process ID,退出狀態 the termination status of the process,運行時間 the amount of CPU time taken by the process等)。父進程可以通過 wait()/waitpid() 來獲取這些信息,然后操作系統才釋放。
這樣就產生了一個問題,如果父進程不調用 wait()/waitpid() 的話,那么保留的信息就不會釋放,其進程號就會一直被占用,就像僵屍一樣,所以把這些進程稱為僵屍進程。
僵屍進程的壞處
上面說到僵屍進程由於父進程不回收系統保留的信息而一直占用着系統資源,其中有一項叫做進程描述符。系統通過分配它來啟動一個進程。
但是系統所能使用的進程號是有限的,如果存在大量的僵屍進程,系統將因為沒有可用的進程號而導致系統不能產生新的進程。
如何查看僵屍進程
僵屍進程在系統中用 <defunct>
或 <z>
表示,通過 ps -ef
指令查看進程,如果發現某個進程的狀態為 <defunct>/<z>
,說明該進程是一個僵屍進程。
避免僵屍進程的方法
通過WAIT/WAITPID系統
在 pcntl 中,父進程可以通過 pcntl_wait() 和 pcntl_waitpid() 函數來等待子進程結束,並回收。
1 <?php 2 $pid = pcntl_fork(); 3 4 if($pid == -1) { 5 die('fork error'); 6 }elseif($pid) { 7 // 父進程阻塞着等待子進程的退出 8 pcntl_wait($status); 9 //pcntl_waitpid($pid, $status); 10 11 // 非阻塞方式,通過WNOHANG區分 12 //pcntl_wait($status, WNOHANG); 13 //pcntl_waitpid($pid, $status, WNOHANG); 14 15 // 如沒有上面的函數,在父進程sleep的時候,子進程就是僵屍進程 16 sleep(10); 17 }else{ 18 echo "child \r\n"; 19 exit; 20 } 21 ?>
int pcntl_wait ( int &$status [, int $options ] ):阻塞當前進程,直到當前進程的一個子進程退出或者收到一個結束當前進程的信號。
int pcntl_waitpid ( int $pid , int &$status [, int $options ] ):功能同 pcntl_wait,區別為 waitpid 為等待指定 pid 的子進程。當 pid 為 -1 時 pcntl_waitpid 與 pcntl_wait 一樣。
阻塞:父進程一直等待,直到收到一個子進程結束的信號再執行;
非阻塞:父進程和子進程同時執行,不用等子進程執行完。在子進程退出后,再回收。
通過SIGCHLD信號
當子進程結束后,系統會像父進程發送 SIGCHLD 信號給父進程,通知父進程子進程已經結束,但父進程默認不處理。我們可以在父進程收到這個信號時調用 wait()/waitpid() 來回收。
1 <?php 2 // 每執行一次低級語句會檢查一次該進程是否有未處理過的信號 3 declare(ticks = 1); 4 5 // 信號處理函數 6 function sig_func() { 7 echo "SIGCHLD \r\n"; 8 9 // 阻塞 10 pcntl_wait($status); 11 //pcntl_waitpid(-1, $status); 12 13 // 非阻塞 14 //pcntl_wait($status, WNOHANG); 15 //pcntl_waitpid(-1, $status, WNOHANG); 16 } 17 18 pcntl_signal(SIGCHLD, 'sig_func'); 19 20 $pid = pcntl_fork(); 21 22 if($pid == -1) { 23 die('fork error'); 24 }elseif($pid) { 25 // 父進程一直執行 26 while(1) { 27 sleep(5); 28 } 29 }else{ 30 echo "child \r\n"; 31 exit; 32 }
在子進程退出后,父進程收到了 SIGCHLD 信號,回調了 sig_func 函數,在內部執行了 pcntl_wait 函數,回收了子進程。
通過操作系統信號
如果父進程不關心子進程什么時候結束,那么可以用 pcntl_signal(SIGCHLD, SIG_IGN)
通知內核,自己對子進程的結束不感興趣,那么子進程結束后,內核會回收,並不再給父進程發送信號。
1 <?php 2 declare(ticks = 1); 3 4 pcntl_signal(SIGCHLD, SIG_IGN); 5 6 $pid = pcntl_fork(); 7 8 if($pid == -1) { 9 die('fork error'); 10 } else if ($pid) { 11 for(;;) { 12 sleep(3); 13 } 14 } else { 15 echo "child \r\n"; 16 exit; 17 }