本文不涉及 linux 系统最底层对信号量的处理,记录主要的内容是代码层在实现一个常驻进程服务时对信号量的处理。 先上一段简单的代码块(循环输出数字到控制台):
Copy $count = 1;
while (true) {
echo $count++.PHP_EOL;
usleep(1000000);
}
在介绍几个函数: a、 这个函数用来设置一段代码的执行指令; b、 该函数用来处理当前进程收到的信号量并转发给指定的函数处理; c、 该函数将执行信号量分发; 那么在一个常驻进程服务中,对信号量的控制如下:
Copy function main () {
echo 'main,pid:'.getmypid().PHP_EOL;
pcntl_signal(SIGTERM,'signalDispatch');
pcntl_signal(SIGINT,'signalDispatch');
$count = 1;
while (true) {
echo $count++.PHP_EOL;
usleep(100000);
pcntl_signal_dispatch();
}
}
Copy function main1 () {
echo 'main,pid:'.getmypid().PHP_EOL;
declare(ticks = 1);
pcntl_signal(SIGTERM,'signalDispatch');
pcntl_signal(SIGINT,'signalDispatch');
$count = 1;
while (true) {
echo $count++.PHP_EOL;
usleep(1000000);
}
}
Copy function signalDispatch($signal) {
switch ($signal) {
case SIGTERM: {
echo 'receive signal 15'.PHP_EOL;
break;
}
case SIGINT: {
echo 'receive signal 2'.PHP_EOL;
break;
}
}
exit(0);
}
这里不得不解释下 pcntl 在信号处理的机制,pcntl 在收到信号后会将其存放在一个队列中,declare 的作用就是每隔 TICK 个 php 语句就检测一次队列,查看是否存在未处理的信号,如果有就挂起程序处理信号;想象一下,一个设置了max_request = 1000 的 worker 进程,有 1000 行 php 代码,那么就需要检测 1000*1000 次,这种方式将严重浪费 CPU 资源。 使用函数 pcntl_signal_dispatch 原理就是在循环中自行处理信号,这样可以大大减少对 CPU 资源的浪费,贴下该函数实现的伪代码:
Copy void pcntl_signal_dispatch()
{
//.... 这里略去一部分代码,queue即是信号队列
while (queue) {
if ((handle = zend_hash_index_find(&PCNTL_G(php_signal_table), queue->signo)) != NULL) {
ZVAL_NULL(&retval);
ZVAL_LONG(¶m, queue->signo);
/* Call php signal handler - Note that we do not report errors, and we ignore the return value */
/* FIXME: this is probably broken when multiple signals are handled in this while loop (retval) */
call_user_function(EG(function_table), NULL, handle, &retval, 1, ¶m TSRMLS_CC);
zval_ptr_dtor(¶m);
zval_ptr_dtor(&retval);
}
next = queue->next;
queue->next = PCNTL_G(spares);
PCNTL_G(spares) = queue;
queue = next;
}
}
这里就搞明白了 php 语言中对信号量处理的函数和方式,不过到目前为止文章收的都是一些很鸡肋的函数使用,一般情况下信号处理都会伴随着多进程一起使用,贴一个最小使用的代码:
Copy function processMain () {
$myPid = getmypid();
$pid = pcntl_fork();
declare(ticks = 1);
pcntl_signal(SIGTERM,'signalDispatch');
pcntl_signal(SIGINT,'signalDispatch');
pcntl_signal(SIGCHLD,'signalDispatch');
if($pid == -1) {
echo "fork process failed!please check system memory!".PHP_EOL;
}
else if($pid == 0) {
$childPid = getmypid();
echo 'fork process successful!my pid is:'.$childPid.PHP_EOL;
}
else {
echo 'I am old process,I am ok,my pid is :'.$myPid.PHP_EOL;
}
$count = 1;
while (true) {
echo $count++.PHP_EOL;
usleep(1000000);
}
}
Copy function signalDispatch($signal) {
$pid = getmypid();
echo "my pid is:{$pid}".PHP_EOL;
switch ($signal) {
case SIGTERM: {
echo 'receive signal 15'.PHP_EOL;
exit(0);
break;
}
case SIGINT: {
echo 'receive signal 2'.PHP_EOL;
exit(0);
break;
}
case SIGCHLD: {
pcntl_wait($status);
echo 'receive signal 17'.PHP_EOL;
break;
}
}
}
可以看到我们引入了两个全新的函数 pcntl_fork 和 pcntl_wait ,pcntl_fork 函数用于复制一个和当前进程完全相同的子进程,而 pcntl_wait 函数则负责在父进程中处理子进程的退出信号,释放系统资源,保障程序的稳定。 至此,一个最简单的 php 版本多进程处理已经完成,有误之处请读者斧正!
参考链接: