在 Workerman 源码中看到这么一段代码,用于终止进程。
<?php
if (static::$_gracefulStop) {
$sig = \SIGTERM;
} else {
$sig = \SIGINT;
}
foreach ($worker_pid_array as $worker_pid) {
\posix_kill($worker_pid, $sig);
if(!static::$_gracefulStop){
Timer::add(static::KILL_WORKER_TIMER_TIME, '\posix_kill', array($worker_pid, \SIGKILL), false);
}
}
以上代码使用了三种信号用于停止进程,分别是SIGTERM
、SIGINT
、SIGKILL
。第一次看到循环中的两个posix_kill
时不禁一愣,为什么要调用两次呢?难道是为了确保成功终止?那为什么不调用三次、四次呢?
在分析之前,再看一段代码。
<?php
pcntl_async_signals(true);
pcntl_signal(SIGKILL, "sig_handler", false);
function sig_handler($signo) {
switch ($signo) {
case SIGKILL:
echo "kill\n";
break;
}
}
while (true) {}
以上代码执行会报错Fatal error: Error installing signal handler for 9
。SIGKILL
的值就是9
,错误的意思是SIGKILL
安装处理器出错,也就是说SIGKILL
是不受用户控制的,无论进程在干什么,都必须要终止。因此,SIGKILL
很靠谱,但是不优雅,毕竟是强制终止的。同时,也可以判断SIGTERM
和SIGINT
都是可以受用户控制的,至于SIGTERM
跟SIGINT
的区别不大,SIGINT
在命令行上可以通过Ctrl+C
发出;SIGTERM
是kill
命令的默认信号。
<?php
// demo.php
pcntl_async_signals(true);
pcntl_signal(SIGINT, "sig_handler", false);
pcntl_signal(SIGTERM, "sig_handler", false);
function sig_handler($signo) {
switch ($signo) {
case SIGINT:
echo "interrupt\n";
break;
case SIGTERM:
echo "terminate\n";
break;
}
}
while (true) {}
执行以上程序,可以玩下以下命令加深一下对信号的印象,^
表示Ctrl
键。
$ php demo.php &
$ ps ajx | grep demo.php | grep -v grep | awk '{print $2}' | xargs kill
terminate
$ jobs
[1]+ Running php demo.php &
$ fg 1
php demo.php
^Cinterrupt
^Cinterrupt
^Cinterrupt
^Z
[1]+ Stopped php demo.php
$ ps ajx
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
16 192 192 16 pts/0 206 T 0 1:35 php demo.php
$ kill -s SIGCONT 192
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
16 192 192 16 pts/0 207 R 0 1:37 php demo.php
除了以上三个信号,还有两个跟停止相关的信号SIGQUIT
和SIGSTOP
。SIGQUIT
跟SIGINT
、SIGTERM
类似,SIGQUIT
可以在命令行上通过Ctrl+\
发出。SIGSTOP
只是暂停进程,并不会终止,在接收到SIGSTOP
之后,SIGTERM
、SIGINT
、SIGQUIT
都会无效,但是SIGKILL
仍然可以终止进程。
在SIGSTOP
之后,通过SIGCONT
信号让进程继续运行。SIGSTOP
和SIGCONT
这对命令挺好用的,可以用于定时器之类的情景下,暂停任务时,通过SIGSTOP
来实现,SIGCONT
则用于恢复任务。