PHP的进程控制PCNTL支持实现了Unix方式的进程创建, 程序执行,信号处理以及进程的中断。 PCNTL只支持linux平台下cli模式,不支持Windows平台,也不能被应用在Web服务器环境(cgi等),当其被用于Web服务环境时可能会带来意外的结果。通常,PCNTL会结合另外一个扩展来使用POSIX来开发(也不支持Windows平台)。
pcntl_fork可以创建一个子进程,父进程和子进程 都从fork的位置开始向下继续执行。创建成功时,父进程得到的返回值是子进程号而子进程得到的返回值是0;创建失败时,父进程得到返回值是-1,不会创建子进程,并触发一个PHP错误。
<?php $pid = pcntl_fork(); //父进程和子进程都会执行下面代码 if ($pid == -1) { //错误处理:创建子进程失败时返回-1. die('could not fork'); } else if ($pid) { //父进程会得到子进程号,所以这里是父进程执行的逻辑 pcntl_wait($status); //等待子进程中断,防止子进程成为僵尸进程。 } else { //子进程得到的$pid为0, 所以这里是子进程执行的逻辑。 }
在对应的父进程结束执行后,子进程就会变成孤儿进程,但之后会立即由init进程(进程ID为1)“收养”为其子进程。
某一子进程终止执行后,若其父进程未提前调用wait,则内核会持续保留子进程的退出状态等信息,以使父进程可以wait获取之[2] 。而因为在这种情况下,子进程虽已终止,但仍在消耗系统资源,所以其亦称僵尸进程。wait常于SIGCHLD信号的处理函数中调用。
为避免产生僵尸进程,一般采取的方式是:将父进程中对SIGCHLD信号的处理函数设为SIG_IGN(忽略信号);fork两次并杀死一级子进程,令二级子进程成为孤儿进程而被init所“收养”、清理。
采用二次创建子进程的方式
<?php $pid = pcntl_fork(); if($pid) { //创建成功,在父进程中执行 echo "run in parent process";//pcntl_wait($status); } else if($pid == -1) { //创建失败,在父进程中处理 echo "Couldn't create child process."; } else { //创建成功,在子进程中执行 //再次创建子进程,即孙进程 $pid = pcntl_fork(); if($pid == 0) { //在孙进程中执行 if(-1 == posix_setsid()) { // 出错退出 exit("Setsid fail"); } echo "run in grandchild process"; } else if($pid == -1) { echo "Couldn’t create child process."; } else { //在子进程中处理 echo "run in child process.";//posix_kill(posix_getpid(), SIGUSR1); exit; } }
通常还会把子进程的pid收集以来,以便监控、回收,如workerman。二次创建子进程通常应用在PHP多进程,守护进程上,比如
<?php defined('DEAMON_LOCK_FILE') || define('DEAMON_LOCK_FILE', 'run/deamon.pid'); if($_SERVER['argc'] >= 2 && $_SERVER['argv'][1] == 'kill') { $fh = fopen(realpath(__DIR__) . '/' . DEAMON_LOCK_FILE, 'r'); $pid = fread($fh, 8); if( $pid ) posix_kill($pid, SIGTERM); exit; } global $DEAMON_LOCK_HANDLER; function daemonize($signalHandler = false ) { global $DEAMON_LOCK_HANDLER; if( ! deamon_file_lock() ) { printf("Deamon is already running...\n"); exit(); } umask(0); $pid = pcntl_fork(); if( $pid < 0 ) { printf("Can't fork\n"); exit; } else if( $pid ) { exit; } $sid = posix_setsid(); if( $sid < 0 ) { printf("Can't set session leader\n"); exit; } deamon_bind_signals($signalHandler); $pid = pcntl_fork(); if( $pid < 0 || $pid ) { exit; } ftruncate($DEAMON_LOCK_HANDLER, 0); fwrite($DEAMON_LOCK_HANDLER, posix_getpid()); chdir('/'); fclose( STDIN ); fclose( STDOUT ); fclose( STDERR ); } function deamon_bind_signals($signalHandler = false) { $signalHandler = !$signalHandler ? "deamon_signal_handler" : $signalHandler; pcntl_signal(SIGTERM, $signalHandler); pcntl_signal(SIGHUP, $signalHandler); pcntl_signal(SIGUSR1, $signalHandler); pcntl_signal(SIGINT, $signalHandler); } function deamon_file_lock() { global $DEAMON_LOCK_HANDLER; $DEAMON_LOCK_HANDLER = fopen(realpath(__DIR__) . '/' . DEAMON_LOCK_FILE, 'c'); if( ! $DEAMON_LOCK_HANDLER ) { printf("Can't open lock file\n"); die(); } if( !flock( $DEAMON_LOCK_HANDLER, LOCK_EX | LOCK_NB ) ) { return false; } return true; } function deamon_signal_handler($signo) { switch( $signo ) { case SIGTERM: case SIGHUP: case SIGUSR1: break; } } function sighandler($sig) { //do something if( $sig == SIGTERM ) { global $DEAMON_LOCK_HANDLER; fclose( $DEAMON_LOCK_HANDLER ); exit; } } daemonize("sighandler"); while( 1 ) { pcntl_signal_dispatch(); // do something here sleep( 1 ); }
可以通过ps -ef | grep php查看过程中的php进程产生情况,CentOS下安装PHP5.4的Posix扩展为:sudo yum instal php54w-process。
pcntl_signal可以注册信号处理函数,捕获信号后交给对应回调函数处理,实现信号通信,例如当某一子进程结束、中断或恢复执行时,内核会发送SIGCHLD信号予其父进程
<?php declare(ticks = 1); pcntl_signal(SIGCHLD, "signal_handler"); function signal_handler($signal) { switch($signal) { case SIGCHLD: while (pcntl_waitpid(0, $status) != -1) { $status = pcntl_wexitstatus($status); echo "Child $status completed\n"; } exit; } } for ($i = 1; $i <= 5; ++$i) { $pid = pcntl_fork(); if (!$pid) { sleep(1); print "In child $i\n"; exit($i); } } while(1) { // parent does processing here... }
pcntl_alarm创建一个计时器,在指定的秒数后向进程发送一个SIGALRM信号,结合pcntl_signal和pcntl_alarm可以做一个秒级的定时器(注意:pcntl_alarm是一次性消耗,需要再次设置)
declare(ticks = 1); function signal_handler($signal) { //do your work here print "Caught SIGALRM\n"; pcntl_alarm(3); } pcntl_signal(SIGALRM, "signal_handler", true); pcntl_alarm(3); while(1) { }
利用PHP的进程控制便可以实现守护进程监控,如socke端口监听;多进程处理,如socke请求事件处理、任务并行、异步处理,提升PHP程序性能。
参考链接:
PHP 进程控制
Getting into multiprocessing
Timing your signals
PHP Deamon
PHP中利用pcntl进行多进程并发控制
PHP高级编程之守护进程
PHP多进程编程一,PHP多进程编程二。
PHP的ticks机制
PHP如何将进程作为守护进程
Daemonising a PHP cli script on a posix system
异步毫秒定时器
The declare() function and ticks
子进程