[原文地址:https://blog.ti-node.com/blog...]php
上一篇尬聊了通篇的pcntl_wait()和pcntl_waitpid(),就是爲了解決殭屍進程的問題,但最後看起來仍是有一些遺留問題,並且由於嘴欠在上篇文章的結尾出也給瞭解決方案:信號。node
信號是一種軟件中斷,也是一種很是典型的異步事件處理方式。在NIX系統誕生的混沌之初,信號的定義是比較混亂的,並且最關鍵是不可靠,這是一個很嚴重的問題。因此在後來的POSIX標準中,對信號作了標準化同時也各個發行版的NIX也都提供大量可靠的信號。每種信號都有本身的名字,大概如SIGTERM、SIGHUP、SIGCHLD等等,在*NIX中,這些信號本質上都是整形數字(遊有心情的能夠參觀一下signal.h系列頭文件)。服務器
信號的產生是有多種方式的,下面是常見的幾種:ssh
而進程在收到信號後,能夠有以下三種響應:異步
用人話來表達,就是說假如你是一個進程,你正在幹活,忽然施工隊的喇叭裏衝你嚷了一句:「吃飯了!」,因而你就放下手裏的活兒去吃飯。你正在幹活,忽然施工隊的喇叭裏衝你嚷了一句:「發工資了!」,因而你就放下手裏的活兒去領工資。你正在幹活,忽然施工隊的喇叭裏衝你嚷了一句:「有人找你!」,因而你就放下手裏的活兒去看看是誰找你什麼事情。固然了,你很任性,那是徹底能夠不鳥喇叭裏喊什麼內容,也就是忽略信號。也能夠更任性,當喇叭裏衝你嚷「吃飯」的時候,你去就不去吃飯,你去睡覺,這些均可以由你來。而你在幹活過程當中,歷來不會由於要等某個信號就不幹活了一直等信號,而是信號隨時隨地均可能會來,而你只須要在這個時候做出相應的迴應便可,因此說,信號是一種軟件中斷,也是一種異步的處理事件的方式。函數
回到上文所說的問題,就是子進程在結束前,父進程就已經先調用了pcntl_waitpid(),致使子進程在結束後依然變成了殭屍進程。實際上在父進程不斷while循環調用pcntl_waitpid()是個解決辦法,大概代碼以下:spa
$pid = pcntl_fork(); if( 0 > $pid ){ exit('fork error.'.PHP_EOL); } else if( 0 < $pid ) { // 在父進程中 cli_set_process_title('php father process'); // 父進程不斷while循環,去反覆執行pcntl_waitpid(),從而試圖解決已經退出的子進程 while( true ){ sleep( 1 ); pcntl_waitpid( $pid, &$status, WNOHANG ); } } else if( 0 == $pid ) { // 在子進程中 // 子進程休眠3秒鐘後直接退出 cli_set_process_title('php child process'); sleep( 20 ); exit; }
下圖是運行結果:rest
可是這樣的代碼有一個缺陷,實際上就是子進程已經退出的狀況下,主進程還在不斷while pcntl_waitpid()去回收子進程,這是一件很奇怪的事情,並不符合社會主義主流價值觀,不低碳不節能,代碼也不優雅,很差看。因此,應該考慮用更好的方式來實現。那麼,咱們篇頭提了許久的信號終於概要出場了。code
如今讓咱們考慮一下,爲什麼信號能夠解決「不低碳不節能,代碼也不優雅,很差看」的問題。子進程在退出的時候,會向父進程發送一個信號,叫作SIGCHLD,那麼父進程一旦收到了這個信號,就能夠做出相應的回收動做,也就是執行pcntl_waitpid(),從而解決掉殭屍進程,並且還顯得咱們代碼優雅好看節能環保。blog
梳理一下流程,子進程向父進程發送SIGCHLD信號是對人們來講是透明的,也就是說咱們無須關心。可是,咱們須要給父進程安裝一個響應SIGCHLD信號的處理器,除此以外,還須要讓這些信號處理器運行起來,安裝上了不運行是一件尷尬的事情。那麼,在php裏給進程安裝信號處理器使用的函數是pcntl_signal(),讓信號處理器跑起來的函數是pcntl_signal_dispatch()。
下面結合新引入的兩個函數來解決一下樓上的醜陋代碼:
$pid = pcntl_fork(); if( 0 > $pid ){ exit('fork error.'.PHP_EOL); } else if( 0 < $pid ) { // 在父進程中 // 給父進程安裝一個SIGCHLD信號處理器 pcntl_signal( SIGCHLD, function() use( $pid ) { echo "收到子進程退出".PHP_EOL; pcntl_waitpid( $pid, $status, WNOHANG ); } ); cli_set_process_title('php father process'); // 父進程不斷while循環,去反覆執行pcntl_waitpid(),從而試圖解決已經退出的子進程 while( true ){ sleep( 1 ); // 註釋掉原來老掉牙的代碼,轉而使用pcntl_signal_dispatch() //pcntl_waitpid( $pid, &$status, WNOHANG ); pcntl_signal_dispatch(); } } else if( 0 == $pid ) { // 在子進程中 // 子進程休眠3秒鐘後直接退出 cli_set_process_title('php child process'); sleep( 20 ); exit; }
運行結果以下: