上一篇文章講解了pcntl_fork
和pcntl_wait
兩個函數的使用,本篇繼續講解PHP多進程相關新知識。php
這裏說下殭屍進程:服務器
殭屍進程是指的父進程已經退出,而該進程dead以後沒有進程接受,就成爲殭屍進程(zombie)進程。任何進程在退出前(使用exit退出) 都會變成殭屍進程(用於保存進程的狀態等信息),而後由init進程接管。若是不及時回收殭屍進程,那麼它在系統中就會佔用一個進程表項,若是這種殭屍進程過多,最後系統就沒有能夠用的進程表項,因而也沒法再運行其它的程序。
經過以下命令查看是否有殭屍進程,若是有,相似下面這樣:swoole
$ ps -A -o stat,ppid,pid,cmd | grep -e '^[Zz]' Z+ 282 283 [php] <defunct>
SIGCHLD
信號處理函數調用wait
或waitpid()
等待子進程結束。那麼子進程結束後,沒有回收,就產生殭屍進程了。示例:
fork_zombie.php函數
<?php $pid = pcntl_fork(); if($pid == -1){ exit("fork fail"); }elseif($pid){ $id = getmypid(); echo "Parent process,pid {$id}, child pid {$pid}\n"; while(1){sleep(3);} //#1 }else{ $id = getmypid(); echo "Child process,pid {$id}\n"; sleep(2); exit(); }
命令行裏運行程序,而後新終端查看:性能
$ ps -A -o stat,ppid,pid,cmd | grep -e '^[Zz]' Z+ 7252 7253 [php] <defunct>
出現了一個殭屍進程。這時候就算手動結束腳本程序也沒法關閉這個殭屍子進程了。須要使用kill -9
關閉。spa
bool pcntl_signal ( int $signo , callback $handler [, bool $restart_syscalls = true ] )
該函數爲signo
指定的信號安裝一個新的信號處理器。命令行
上一節裏,咱們講到殭屍進程產生的緣由:rest
若是父進程是循環,又沒有安裝SIGCHLD
信號處理函數調用wait
或waitpid()
等待子進程結束。那麼子進程結束後,沒有回收,就產生殭屍進程了。
本小節咱們經過安裝SIGCHLD信號處理函數來解決殭屍進程問題。示例:code
<?php //表示每執行一條低級指令,就檢查一次信號,若是檢測到註冊的信號,就調用其信號處理器 declare(ticks = 1); //安裝SIGCHLD信號 pcntl_signal(SIGCHLD, function(){ echo "SIGCHLD \r\n"; pcntl_wait($status); }); //#2 $pid = pcntl_fork(); if($pid == -1){ exit("fork fail"); }elseif($pid){ $id = getmypid(); echo "Parent process,pid {$id}, child pid {$pid}\n"; //先sleep一下,不然代碼一直循環,沒法處理信號接收 while(1){sleep(3);} //#1 }else{ $id = getmypid(); echo "Child process,pid {$id}\n"; sleep(2); exit(); }
第一次註釋掉#1
和#2
處的代碼,父進程提早結束,子進程被init進程接手,因此沒有產生殭屍進程。
第二次咱們註釋掉#2
處的代碼,開啓#1
處的代碼,即父進程是個死循環,又沒有回收子進程,就產生殭屍進程了。
第三次咱們開啓#1
處和#2
處的代碼,父進程因爲安裝了信號處理,並調用wait函數等待子進程結束,因此也沒有產生殭屍進程。blog
對子進程的結束不感興趣
若是父進程不關心子進程何時結束,那麼能夠用pcntl_signal(SIGCHLD, SIG_IGN)
通知內核,本身對子進程的結束不感興趣,那麼子進程結束後,內核會回收,並再也不給父進程發送信號。這樣咱們就不寫子進程退出的處理函數了。
說明:
若是去掉
declare( ticks = 1 );
沒法響應信號。因php的信號處理函數是基於ticks來實現的,而不是註冊到真正系統底層的信號處理函數中。
咱們能夠在主進程安裝更多信號,例如:
<?php declare( ticks = 1 ); //信號處理函數 function sig_handler ( $signo ) { switch ( $signo ) { case SIGTERM : // 處理SIGTERM信號 exit; break; case SIGHUP : //處理SIGHUP信號 break; case SIGUSR1 : echo "Caught SIGUSR1...\n" ; break; default: // 處理全部其餘信號 } } echo "Installing signal handler...\n" ; //安裝信號處理器 pcntl_signal ( SIGTERM , "sig_handler" ); pcntl_signal ( SIGHUP , "sig_handler" ); pcntl_signal ( SIGUSR1 , "sig_handler" ); echo "Generating signal SIGTERM to self...\n" ; //向當前進程發送SIGUSR1信號 posix_kill ( posix_getpid (), SIGUSR1 ); echo "Done\n"
注:經過
kill -l
能夠看到Linux下全部的信號常量。
$ kill -l 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 63) SIGRTMAX-1 64) SIGRTMAX
防盜版聲明:本文系原創文章,發佈於公衆號飛鴻影的博客(fhyblog)
及博客園,轉載需做者贊成。
PHP的ticks=1
表示每執行1行PHP代碼就回調此函數(指的pcntl_signal_dispatch
)。實際上大部分時間都沒有信號產生,但ticks的函數一直會執行。若是一個服務器程序1秒中接收1000次請求,平均每一個請求要執行1000行PHP代碼。那麼PHP的pcntl_signal,就帶來了額外的 1000 * 1000,也就是100萬次空的函數調用。這樣會浪費大量的CPU資源。
(摘自:韓天峯(Rango)的博客 » PHP官方的pcntl_signal性能極差
http://rango.swoole.com/archi...
pcntl_signal_dispatch的做用就是查看是否收到了信號須要處理,若是有信號的話,就調用相應的信號處理函數。
因此上述問題比較好的作法是去掉ticks,轉而手動調用pcntl_signal_dispatch
,在代碼循環中自行處理信號。
咱們把上一小節的例子改改,不使用ticks:
<?php //declare( ticks = 1 ); //信號處理函數 function sig_handler ( $signo ) { switch ( $signo ) { case SIGUSR1 : echo "Caught SIGUSR1...\n" ; break; default: // 處理全部其餘信號 } } echo "Installing signal handler...\n" ; //安裝信號處理器 pcntl_signal ( SIGUSR1 , "sig_handler" ); echo "Generating signal SIGTERM to self...\n" ; //向當前進程發送SIGUSR1信號 posix_kill ( posix_getpid (), SIGUSR1 ); pcntl_signal_dispatch(); echo "Done\n";
運行結果:
Installing signal handler... Generating signal SIGTERM to self... Caught SIGUSR1... Done
相比每執行一條php語句都會調用 pcntl_signal_dispatch
一次,效率好多了。
int pcntl_alarm ( int $seconds )
該函數建立一個計時器,在指定的秒數後向進程發送一個 SIGALRM
信號。每次對 pcntl_alarm()
的調用都會取消以前設置的alarm信號。注意不是定時器,只會運行一次。
下面是一個隔5秒發送一個SIGALRM信號,並由signal_handler函數獲取,而後打印一個 SIGALRM
的例子:
<?php declare(ticks = 1); //安裝SIGALRM信號 pcntl_signal(SIGALRM, function(){ echo "SIGALRM\n"; pcntl_alarm(5); //再次調用,會從新發送一個SIGALRM信號 }); pcntl_alarm(5);//發送一個SIGALRM信號 echo "run...\n"; //死循環,不然進程會退出 while(1){sleep(1);}
注:若是不想使用ticks,那麼須要在主循環裏主動增長
pcntl_signal_dispatch()
調用。
(未完待續)
歡迎關注公衆號及時獲取最新文章推送!
推薦!每個月僅需$2.5,便可擁有配置SSD的VPS!