创建PHP守护进程

9年6个月前发布   4802浏览   13评论

守护进程(精灵进程):

- 守护进程是生存周期长的一种进程。一般在系统引导装入时启动,在系统关闭时终止。
- 所有守护进程都已超级用户(用户ID)的优先权运行 。
- 守护进程没有控制端。
- 守护进程的父进程都是init进程。

如何创建一个守护进程:

1. 适用umask将文件模式创建屏蔽字设为0,让其不受读写影响。
2. 调用fork,然后让父进程退出,使其变成孤儿进程,自动被init进程收养。
3. 调用setsid创建一个新会话,脱离控制终端。
4. 将当前工作目录更改为根目录,因为磁盘是挂载在Linux上的,如果卸载掉这些磁盘,目录也不回存在了。而根目录是一直存在的,这样进程不回受到影响。
5. 关闭不需要的文件描述符

相关代码实现:
public function main(){
umask(0) ;
$pid = pcntl_fork() ;
if($pid < 0){
die('fork new process fail') ;
}else if($pid){
exit ; //exit master process
}
//the child process
chdir("/") ;
fclose(STDIN);
fclose(STDOUT);
fclose(STDERR);
$STDIN = fopen('/dev/null', 'r');
$STDOUT = fopen('/dev/null', 'wb');
$STDERR = fopen('/dev/null', 'wb');

// make it session leader
posix_setsid();
for(;;){
sleep(10) ;
pcntl_signal_dispatch();
}
}
//注册信号回调函数(用户杀死进程)
pcntl_signal(SIGINT, "cleanup");
public function cleanup(){
echo "the ".getmypid() ." is cleanup" ;
exit() ;
}

ticks、sleep信号唤醒、信号注册等:

何为ticks?

进行信号处理的时候pcntl_signal往往要配合ticks使用(性能差),何为ticks?ticks就是在每n【declare(ticks = 1)】条语句中插入你的tick回调函数,如:
$a = 1;
tickCallBack();
$b = 2;
tickCallBack();
$c = 3 ;
tickCallBack();
....

pcntl_signal与ticks的配合:

为什么它会会配合pcntl_signal呢?我们知道pcntl_signal可以注册信号回调函数,当触发一个信号的时候,就会把对应的信号函数加入一个队列中,而ticks不仅仅会调用通过register_tick_function注册的tick函数,还会去查看有没有信号队列,有就回调指定的信号回调函数。
PHP_MINIT_FUNCTION(pcntl)
{
php_register_signal_constants(INIT_FUNC_ARGS_PASSTHRU);
php_pcntl_register_errno_constants(INIT_FUNC_ARGS_PASSTHRU);
php_add_tick_function(pcntl_signal_dispatch TSRMLS_CC);
return SUCCESS;
}
假设此时没有任何信号,如果并发1000个请求,每个请求执行2000行代码,那么就是200W个空函数回调了。绝对的浪费cpu资源。

pcntl_signal_dispatch性能比较好:

php5.3以后可以用pcntl_signal_dispatch来进行信号的触发。同样的,他会调用在这一期间,所有存储在信号队列的信号回调函数,而不是没执行n跳语句就去调用一下信号回调函数。这样一来pcntl_signal_dispatch()方法什么时候调用就需要自己的业务逻辑来判断了。
//注册信号回调函数
pcntl_signal(SIGINT, "sigintCallBack");

//回调函数
public function sigintCallBack(){
echo "In SIGINT func\n";
}

for($i=0;$i<2;$i++){
sleep(10) ;
pcntl_signal_dispatch()
}

//在控制台中按contril+c
//打印:In SIGINT func

sleep会被信号唤醒:

上面的例子中,发送中断信号会让程序立刻从睡眠中唤醒(此时sleep会返回剩余的睡眠时间)并保存一个中断信号函数到队列中,之后遇到了pcntl_signal_dispatch方法去触发信号回调函数。

pcntl_signal_dispatch底层实现:
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(&param, 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, &param TSRMLS_CC);
zval_ptr_dtor(&param);
zval_ptr_dtor(&retval);
}
next = queue->next;
queue->next = PCNTL_G(spares);
PCNTL_G(spares) = queue;
queue = next;
}
在没有为进程注册中断回调函数的适合,我们control+c就直接退出进程,但是如果指定了SIGINT中断函数,睡眠经常会被一些信号(SIGINT,SIGALRM)中断,如果不是以ticks形式配合信号,信号回调函数不会立刻执行而是放入信号队列,在遇到对应的触发函数如pcntl_signal_dispatch后触发。

我们可以利用它返回的剩余时间让进程继续睡眠:
$left = sleep(5);
echo "left $a seconds\n" ;
pcntl_signal_dispatch();
while($left > 0){
$left = sleep($left) ;
echo "left $left secondssss\n" ;
pcntl_signal_dispatch();
}
评论

Andy

牛逼!凑足5个字
9年6个月前

﹏雪〆克彡✅已认证❶

我也就是来瞅瞅
8年6个月前

老马马

[可爱]
8年5个月前

{"name":"路人黄"}

[哈哈]
8年5个月前

{"name":"路人黄"}

[哈哈]
8年5个月前

站挺不错的 新颖
8年5个月前

A Dream

[生病]
8年5个月前

Amorº 止于命

<script>alert(123)</script>
8年2个月前
发表评论,请先

游客