PHP多進程初探 --- 信號

[原文地址:https://blog.ti-node.com/blog...]php

上一篇尬聊了通篇的pcntl_wait()和pcntl_waitpid(),就是爲了解決殭屍進程的問題,但最後看起來仍是有一些遺留問題,並且由於嘴欠在上篇文章的結尾出也給瞭解決方案:信號。node

信號是一種軟件中斷,也是一種很是典型的異步事件處理方式。在NIX系統誕生的混沌之初,信號的定義是比較混亂的,並且最關鍵是不可靠,這是一個很嚴重的問題。因此在後來的POSIX標準中,對信號作了標準化同時也各個發行版的NIX也都提供大量可靠的信號。每種信號都有本身的名字,大概如SIGTERM、SIGHUP、SIGCHLD等等,在*NIX中,這些信號本質上都是整形數字(遊有心情的能夠參觀一下signal.h系列頭文件)。服務器

信號的產生是有多種方式的,下面是常見的幾種:ssh

  • 鍵盤上按某些組合鍵,好比Ctrl+C或者Ctrl+D等,會產生SIGINT信號。
  • 使用posix kill調用,能夠向某個進程發送指定的信號。
  • 遠程ssh終端狀況下,若是你在服務器上執行了一個阻塞的腳本,正在阻塞過程當中你關閉了終端,可能就會產生SIGHUP信號。
  • 硬件也會產生信號,好比OOM了或者遇到除0這種狀況,硬件也會向進程發送特定信號。

而進程在收到信號後,能夠有以下三種響應:異步

  • 直接忽略,不作任何反映。就是俗稱的徹底不鳥。可是有兩種信號,永遠不會被忽略,一個是SIGSTOP,另外一個是SIGKILL,由於這兩個進程提供了向內核最後的可靠的結束進程的辦法。
  • 捕捉信號並做出相應的一些反應,具體響應什麼能夠由用戶本身經過程序自定義。
  • 系統默認響應。大多數進程在遇到信號後,若是用戶也沒有自定義響應,那麼就會採起系統默認響應,大多數的系統默認響應就是終止進程。

用人話來表達,就是說假如你是一個進程,你正在幹活,忽然施工隊的喇叭裏衝你嚷了一句:「吃飯了!」,因而你就放下手裏的活兒去吃飯。你正在幹活,忽然施工隊的喇叭裏衝你嚷了一句:「發工資了!」,因而你就放下手裏的活兒去領工資。你正在幹活,忽然施工隊的喇叭裏衝你嚷了一句:「有人找你!」,因而你就放下手裏的活兒去看看是誰找你什麼事情。固然了,你很任性,那是徹底能夠不鳥喇叭裏喊什麼內容,也就是忽略信號。也能夠更任性,當喇叭裏衝你嚷「吃飯」的時候,你去就不去吃飯,你去睡覺,這些均可以由你來。而你在幹活過程當中,歷來不會由於要等某個信號就不幹活了一直等信號,而是信號隨時隨地均可能會來,而你只須要在這個時候做出相應的迴應便可,因此說,信號是一種軟件中斷,也是一種異步的處理事件的方式。函數

回到上文所說的問題,就是子進程在結束前,父進程就已經先調用了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

解析一下這個結果,我前後三次執行了ps -aux | grep php去查看這兩個php進程。
  • 第一次:子進程正在休眠中,父進程依舊在循環中。
  • 第二次:子進程已經退出了,父進程依舊在循環中,可是代碼尚未執行到pcntl_waitpid(),因此在子進程退出後到父進程執行回收前這段空隙內子進程變成了殭屍進程。
  • 第三次:此時父進程已經執行了pcntl_waitpid(),將已經退出的子進程回收,釋放了pid等資源。

可是這樣的代碼有一個缺陷,實際上就是子進程已經退出的狀況下,主進程還在不斷while pcntl_waitpid()去回收子進程,這是一件很奇怪的事情,並不符合社會主義主流價值觀,不低碳不節能,代碼也不優雅,很差看。因此,應該考慮用更好的方式來實現。那麼,咱們篇頭提了許久的信號終於概要出場了。code

如今讓咱們考慮一下,爲什麼信號能夠解決「不低碳不節能,代碼也不優雅,很差看」的問題。子進程在退出的時候,會向父進程發送一個信號,叫作SIGCHLD,那麼父進程一旦收到了這個信號,就能夠做出相應的回收動做,也就是執行pcntl_waitpid(),從而解決掉殭屍進程,並且還顯得咱們代碼優雅好看節能環保。blog

梳理一下流程,子進程向父進程發送SIGCHLD信號是對人們來講是透明的,也就是說咱們無須關心。可是,咱們須要給父進程安裝一個響應SIGCHLD信號的處理器,除此以外,還須要讓這些信號處理器運行起來,安裝上了不運行是一件尷尬的事情。那麼,在php裏給進程安裝信號處理器使用的函數是pcntl_signal(),讓信號處理器跑起來的函數是pcntl_signal_dispatch()。

  • pcntl_signal(),安裝一個信號處理器,具體說明是pcntl_signal ( int $signo , callback $handler [, bool $restart_syscalls = true ] ),參數signo就是信號,callback則是響應該信號的代碼段,返回bool值。
  • pcntl_signal_dispatch(),調用每一個等待信號經過pcntl_signal() 安裝的處理器,參數爲void,返回bool值。

下面結合新引入的兩個函數來解決一下樓上的醜陋代碼:

$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;
}

運行結果以下:


[原文地址:https://blog.ti-node.com/blog...]

相關文章
相關標籤/搜索