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
子进程
Pingback引用通告: PHP 进程间通信 | 勇气
Pingback引用通告: PHP 事件驱动开发 | 勇气