摘要:孤兒進程是指父進程在出子進程后,自己先完了。這個問題很尷尬,因為子進程從此變得無依無靠無家可歸,變成了孤兒。在中,父進程對子進程的狀態收集等是通過和等完成的。這個函數返回退出的子進程的進程或者失敗返回。
[原文地址:https://blog.ti-node.com/blog...]
實際上,你們一定要記住:PHP的多進程是非常值得應用于生產環境具備高價值的生產力工具。
但我認為在正式開始吹牛之前還是要說兩個基本概念:孤兒進程、僵尸進程。
上篇我整篇尬聊的都是pcntl_fork(),只管fork生產,不管產后護理,實際上這樣并不符合主流價值觀,而且,操作系統本身資源有限,這樣無限生產不顧護理,操作系統也會吃不消的。
孤兒進程是指父進程在fork出子進程后,自己先完了。這個問題很尷尬,因為子進程從此變得無依無靠、無家可歸,變成了孤兒。用術語來表達就是,父進程在子進程結束之前提前退出,這些子進程將由init(進程ID為1)進程收養并完成對其各種數據狀態的收集。init進程是Linux系統下的奇怪進程,這個進程是以普通用戶權限運行但卻具備超級權限的進程,簡單地說,這個進程在Linux系統啟動的時候做初始化工作,比如運行getty、比如會根據/etc/inittab中設置的運行等級初始化系統等等,當然了,還有一個作用就是如上所說的:收養孤兒進程。
僵尸進程是指父進程在fork出子進程,而后子進程在結束后,父進程并沒有調用wait或者waitpid等完成對其清理善后工作,導致改子進程進程ID、文件描述符等依然保留在系統中,極大浪費了系統資源。所以,僵尸進程是對系統有危害的,而孤兒進程則相對來說沒那么嚴重。在Linux系統中,我們可以通過ps -aux來查看進程,如果有[Z+]標記就是僵尸進程。
在PHP中,父進程對子進程的狀態收集等是通過pcntl_wait()和pcntl_waitpid()等完成的。依然還是要通過代碼還演示說明:
演示并說明孤兒進程的出現,并演示孤兒進程被init進程收養:
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; }
運行結果如下圖:
可以看到,前兩秒內,子進程的父進程進程ID為4129,但是從第三秒開始,由于父進程已經提前退出了,子進程變成孤兒進程,所以init進程收養了子進程,所以子進程的父進程進程ID變成了1。
演示并說明僵尸進程的出現,并演示僵尸進程的危害:
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。
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()功能:
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,代碼如下:
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()如何正確使用啊?這樣用,看起來畢竟不太科學。
那么,是時候引入信號學了!
[原文地址:https://blog.ti-node.com/blog...]
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/29329.html
摘要:多進程中與多進程相關的兩個重要拓展是和。函數執行期間,主進程除了等待無法處理其他任務,所以一般不認為這是多進程編程。回收子進程有兩種方式,一種是主進程調用函數等待子進程結束另外一種是處理信號。 轉載請注明文章出處: https://tlanyan.me/php-review... PHP回顧系列目錄 PHP基礎 web請求 cookie web響應 session 數據庫操作 加解...
摘要:守護進程是在一類脫離終端在后臺執行的程序通常以結尾隨系統啟動其父進程通常是進程一般要讓當前程序以守護進程形式運行在命令后加并重定向輸出即可或者使用也可以這是直接運行程序的方式如果是用具體語言代碼的形式來實現呢首先看一下守護進程的實現方式創建 守護進程是在一類脫離終端在后臺執行的程序, 通常以d結尾, 隨系統啟動, 其父進程(ppid)通常是init進程 一般要讓當前程序以守護進程形式運...
摘要:運行模式實現進程前,需了解常見的的運行模式通用網關接口模式模式命令行模式模塊模式作為服務器模塊而進程則是使用命令行模式運行的基本實現中提供了一個擴展,可以利用操作系統的調用來實現多進程。 應用場景 一些耗時任務: 大數據表分表后的統計信息功能 分批發送短信或郵件功能 其他可分目標的任務功能(很多種) 所以我們就需要一個常駐內存的任務管理工具,為了保證實時性,一方面我們讓它一直執行任...
摘要:運行模式實現進程前,需了解常見的的運行模式通用網關接口模式模式命令行模式模塊模式作為服務器模塊而進程則是使用命令行模式運行的基本實現中提供了一個擴展,可以利用操作系統的調用來實現多進程。 應用場景 一些耗時任務: 大數據表分表后的統計信息功能 分批發送短信或郵件功能 其他可分目標的任務功能(很多種) 所以我們就需要一個常駐內存的任務管理工具,為了保證實時性,一方面我們讓它一直執行任...
閱讀 3476·2021-11-19 09:40
閱讀 1492·2021-10-13 09:41
閱讀 2655·2021-09-29 09:35
閱讀 2710·2021-09-23 11:21
閱讀 1693·2021-09-09 11:56
閱讀 830·2019-08-30 15:53
閱讀 844·2019-08-30 15:52
閱讀 598·2019-08-30 12:47