[原文地址:https://blog.ti-node.com/blog...]php
實際上,大家必定要記住:PHP的多進程是很是值得應用於生產環境具有高價值的生產力工具。node
但我認爲在正式開始吹牛以前仍是要說兩個基本概念:孤兒進程、殭屍進程。編程
上篇我整篇尬聊的都是pcntl_fork(),只管fork生產,無論產後護理,實際上這樣並不符合主流價值觀,並且,操做系統自己資源有限,這樣無限生產不顧護理,操做系統也會吃不消的。函數
孤兒進程是指父進程在fork出子進程後,本身先完了。這個問題很尷尬,由於子進程今後變得無依無靠、無家可歸,變成了孤兒。用術語來表達就是,父進程在子進程結束以前提早退出,這些子進程將由init(進程ID爲1)進程收養並完成對其各類數據狀態的收集。init進程是Linux系統下的奇怪進程,這個進程是以普通用戶權限運行但卻具有超級權限的進程,簡單地說,這個進程在Linux系統啓動的時候作初始化工做,好比運行getty、好比會根據/etc/inittab中設置的運行等級初始化系統等等,固然了,還有一個做用就是如上所說的:收養孤兒進程。工具
殭屍進程是指父進程在fork出子進程,然後子進程在結束後,父進程並無調用wait或者waitpid等完成對其清理善後工做,致使改子進程進程ID、文件描述符等依然保留在系統中,極大浪費了系統資源。因此,殭屍進程是對系統有危害的,而孤兒進程則相對來講沒那麼嚴重。在Linux系統中,咱們能夠經過ps -aux來查看進程,若是有[Z+]標記就是殭屍進程。spa
在PHP中,父進程對子進程的狀態收集等是經過pcntl_wait()和pcntl_waitpid()等完成的。依然仍是要經過代碼還演示說明:操作系統
演示並說明孤兒進程的出現,並演示孤兒進程被init進程收養:code
<?php $pid = pcntl_fork(); if( $pid > 0 ){ // 顯示父進程的進程ID,這個函數能夠是getmypid(),也能夠用posix_getpid() echo "Father PID:".getmypid().PHP_EOL; // 讓父進程中止兩秒鐘,在這兩秒內,子進程的父進程ID仍是這個父進程 sleep( 2 ); } else if( 0 == $pid ) { // 讓子進程循環10次,每次睡眠1s,而後每秒鐘獲取一次子進程的父進程進程ID for( $i = 1; $i <= 10; $i++ ){ sleep( 1 ); // posix_getppid()函數的做用就是獲取當前進程的父進程進程ID echo posix_getppid().PHP_EOL; } } else { echo "fork error.".PHP_EOL; }
運行結果以下圖:
blog
能夠看到,前兩秒內,子進程的父進程進程ID爲4129,可是從第三秒開始,因爲父進程已經提早退出了,子進程變成孤兒進程,因此init進程收養了子進程,因此子進程的父進程進程ID變成了1。進程
演示並說明殭屍進程的出現,並演示殭屍進程的危害:
<?php $pid = pcntl_fork(); if( $pid > 0 ){ // 下面這個函數能夠更改php進程的名稱 cli_set_process_title('php father process'); // 讓主進程休息60秒鐘 sleep(60); } else if( 0 == $pid ) { cli_set_process_title('php child process'); // 讓子進程休息10秒鐘,可是進程結束後,父進程不對子進程作任何處理工做,這樣這個子進程就會變成殭屍進程 sleep(10); } else { exit('fork error.'.PHP_EOL); }
運行結果以下圖:
經過執行ps -aux命令能夠看到,當程序在前十秒內運行的時候,php child process的狀態列爲[S+],然而在十秒鐘事後,這個狀態變成了[Z+],也就是變成了危害系統的殭屍進程。
那麼,問題來了?如何避免殭屍進程呢?PHP經過pcntl_wait()和pcntl_waitpid()兩個函數來幫咱們解決這個問題。瞭解Linux系統編程的應該知道,看名字就知道這其實就是PHP把C語言中的wait()和waitpid()包裝了一下。
經過代碼演示pcntl_wait()來避免殭屍進程,在開始以前先簡單普及一下pcntl_wait()的相關內容:這個函數的做用就是 「 等待或者返回子進程的狀態 」,當父進程執行了該函數後,就會阻塞掛起等待子進程的狀態一直等到子進程已經因爲某種緣由退出或者終止。換句話說就是若是子進程還沒結束,那麼父進程就會一直等等等,若是子進程已經結束,那麼父進程就會馬上獲得子進程狀態。這個函數返回退出的子進程的進程ID或者失敗返回-1。
<?php $pid = pcntl_fork(); if( $pid > 0 ){ // 下面這個函數能夠更改php進程的名稱 cli_set_process_title('php father process'); // 返回$wait_result,就是子進程的進程號,若是子進程已是殭屍進程則爲0 // 子進程狀態則保存在了$status參數中,能夠經過pcntl_wexitstatus()等一系列函數來查看$status的狀態信息是什麼 $wait_result = pcntl_wait( $status ); print_r( $wait_result ); print_r( $status ); // 讓主進程休息60秒鐘 sleep(60); } else if( 0 == $pid ) { cli_set_process_title('php child process'); // 讓子進程休息10秒鐘,可是進程結束後,父進程不對子進程作任何處理工做,這樣這個子進程就會變成殭屍進程 sleep(10); } else { exit('fork error.'.PHP_EOL); }
將文件保存爲wait.php,而後php wait.php,在另一個終端中經過ps -aux查看,能夠看到在前十秒內,php child process是[S+]狀態,而後十秒鐘事後進程消失了,也就是被父進程回收了,沒有變成殭屍進程。
可是,pcntl_wait()有個很大的問題,就是阻塞。父進程只能掛起等待子進程結束或終止,在此期間父進程什麼都不能作,這並不符合多快好省原則,因此pcntl_waitpid()閃亮登場。pcntl_waitpid( $pid, &$status, $option = 0 )的第三個參數若是設置爲WNOHANG,那麼父進程不會阻塞一直等待到有子進程退出或終止,不然將會和pcntl_wait()的表現相似。
修改第三個案例的代碼,可是,咱們並不添加WNOHANG,演示說明pcntl_waitpid()功能:
<?php $pid = pcntl_fork(); if( $pid > 0 ){ // 下面這個函數能夠更改php進程的名稱 cli_set_process_title('php father process'); // 返回值保存在$wait_result中 // $pid參數表示 子進程的進程ID // 子進程狀態則保存在了參數$status中 // 將第三個option參數設置爲常量WNOHANG,則能夠避免主進程阻塞掛起,此處父進程將當即返回繼續往下執行剩下的代碼 $wait_result = pcntl_waitpid( $pid, $status ); var_dump( $wait_result ); var_dump( $status ); // 讓主進程休息60秒鐘 sleep(60); } else if( 0 == $pid ) { cli_set_process_title('php child process'); // 讓子進程休息10秒鐘,可是進程結束後,父進程不對子進程作任何處理工做,這樣這個子進程就會變成殭屍進程 sleep(10); } else { exit('fork error.'.PHP_EOL); }
下面是運行結果,一個執行php程序的終端窗口,另外一個是ps -aux終端窗口。實際上能夠看到主進程是被阻塞的,一直到第十秒子進程退出了,父進程再也不阻塞:
那麼咱們修改第四段代碼,添加第三個參數WNOHANG,代碼以下:
<?php $pid = pcntl_fork(); if( $pid > 0 ){ // 下面這個函數能夠更改php進程的名稱 cli_set_process_title('php father process'); // 返回值保存在$wait_result中 // $pid參數表示 子進程的進程ID // 子進程狀態則保存在了參數$status中 // 將第三個option參數設置爲常量WNOHANG,則能夠避免主進程阻塞掛起,此處父進程將當即返回繼續往下執行剩下的代碼 $wait_result = pcntl_waitpid( $pid, $status, WNOHANG ); var_dump( $wait_result ); var_dump( $status ); echo "不阻塞,運行到這裏".PHP_EOL; // 讓主進程休息60秒鐘 sleep(60); } else if( 0 == $pid ) { cli_set_process_title('php child process'); // 讓子進程休息10秒鐘,可是進程結束後,父進程不對子進程作任何處理工做,這樣這個子進程就會變成殭屍進程 sleep(10); } else { exit('fork error.'.PHP_EOL); }
面是運行結果,一個執行php程序的終端窗口,另外一個是ps -aux終端窗口。實際上能夠看到主進程是被阻塞的,一直到第十秒子進程退出了,父進程再也不阻塞:
咱們看到子進程是睡眠了十秒鐘,而父進程在執行pcntl_waitpid()以前沒有任何睡眠且自己再也不阻塞,因此,主進程本身先執行下去了,而子進程在足足十秒鐘後才結束,進程狀態天然沒法獲得回收。若是咱們將代碼修改一下,就是在主進程的pcntl_waitpid()前睡眠15秒鐘,這樣就能夠回收子進程了。可是即使這樣修改,細心想的話仍是會有個問題,那就是在子進程結束後,在父進程執行pcntl_waitpid()回收前,有五秒鐘的時間差,在這個時間差內,php child process也將會是殭屍進程。那麼,pcntl_waitpid()如何正確使用啊?這樣用,看起來畢竟不太科學。
那麼,是時候引入信號學了!