标签归档:pcntl

PHP 进程控制PCNTL

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
子进程