主页
主页
文章目录
  1. SIG 列表
  2. Golang中的信号处理
  3. PHP中的信号处理
    1. 方案一 ticks
    2. 方案二 pcntl_signal_dispatch
    3. 方案三 pcntl_async_signals

实例探索进程信号处理-优雅退出

应用程序在处理工作的过程中通常会申请很多系统资源,通常我们希望在应用程序结束前提前释放资源,也就是优雅退出。这时候SIG信号处理就派上用场了。
之前使用Golang的时候通常会保证服务是优雅退出的,会保证正在运行的Goroutine都stop之后,主Goroutine才退出。近期在做PHP相关项目时,会启动一些常驻脚本执行任务,于是想在脚本中增加优雅退出机制。

SIG 列表

在Linux终端下我们执行 kill -l 可以看到POSIX中定义的信号列表。
kill list

但是在linux系统中,SIGKILL(9号信号)和 SIGSTOP (19号信号)这两个信号是无法被我们自己捕获和处理的,SIGKILL总是会结束进程运行,SIGSTOP总是能暂停进程。

Golang中的信号处理

在Golang中我们是使用os/signal包来处理信号的。一个简单的实例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"fmt"
"os"
"os/signal"
"syscall"
)

// 监听指定信号
func main() {
//合建chan
c := make(chan os.Signal)
//监听指定信号 ctrl+c kill
signal.Notify(c, os.Interrupt, os.Kill, syscall.SIGUSR1, syscall.SIGUSR2)
//阻塞直到有信号传入
fmt.Println("启动")
//阻塞直至有信号传入
s := <-c
fmt.Println("退出信号", s)
}

PHP中的信号处理

在PHP中提供了pcntl扩展以及posix扩展可以让我们来操作信号。

1
2
3
4
5
6
7
8
pcntl_signal_dispatch — 调用等待信号的处理器
pcntl_signal_get_handler — Get the current handler for specified signal
pcntl_signal — 安装一个信号处理器
pcntl_sigprocmask — 设置或检索阻塞信号
pcntl_sigtimedwait — 带超时机制的信号等待
pcntl_sigwaitinfo — 等待信号

posix_kill — 向一个进程发送信号
方案一 ticks

在官方文档里是这样写的:
As of PHP 4.3.0 PCNTL uses ticks as the signal handle callback mechanism, which is much faster than the previous mechanism. This change follows the same semantics as using "user ticks". You must use the declare() statement to specify the locations in your program where callbacks are allowed to occur for the signal handler to function properly (as used in the example below).
意思是在4.3.0之后版本PCNTL使用ticks捕获触发信号处理函数。
官方的pcntl_signal性能比较差,主要是PHP的函数无法直接注册到操作系统信号设置中,所以在老版本的PHP里pcntl信号需要依赖tick机制来完成。

pcntl_signal的实现原理是,维护一个信号队列,有信号后会Push进入队列,然后在ticks回调函数中检查是否有信号,有的话就执行信号对应的处理函数。
ticks通过declare定义:declare(ticks = 1);

ticks=1表示每执行1行PHP低级代码就回调此函数。实际过程中大部分运行时间是没有信号的,所以这样就会导致每次语句都会不必要的执行检查,导致系统性能有所下降。而且当有语句进行阻塞无法调用时,进程会一直hang住,接收不到任何信号。

方案二 pcntl_signal_dispatch

在PHP5.3.0版本以后增加了pcntl_signal_dispatch函数,我们可以在需要等待信号的地方调用该函数,来执行信号回调。这样我们既降低了代码执行频率也实现了类实时的信号通知。
示例如下:
cliBase.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<?php
class CliBase
{
public $stop = false;

public function __construct()
{
try {

$this->registerSigHandler();

echo ('cliBase, init done, pid='.posix_getpid());

} catch (Exception $e) {
echo "cliBase, init failed";
exit();
}
}

public function registerSigHandler(){
if (function_exists('pcntl_signal')) {
$ret =pcntl_signal(SIGINT, array($this, "sigHandler"));
$ret =pcntl_signal(SIGTERM, array($this, "sigHandler"));
$ret = pcntl_signal(SIGQUIT, array($this, "sigHandler"));
$ret = pcntl_signal(SIGUSR1, array($this, "sigHandler"));
}
}


public function sigHandler($signo)
{
switch ($signo) {
case SIGTERM:
echo "Catch sig\n";
$this->stop = true;
break;
case SIGINT:
echo "Catch sig\n";
$this->stop = true;
break;
case SIGQUIT:
echo "Catch sig\n";
$this->stop = true;
break;
case SIGUSR1:
echo "Catch sig\n";
$this->stop = true;
break;
default:
echo "Catch sig\n";
$this->stop = true;
break;
}
}

}

logic.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
include 'cliBase.php';
class Logic extends CliBase
{

public function main()
{

while (true) {
pcntl_signal_dispatch();
if ($this->stop) {
echo ('graceful exit');
exit();
}

// Do something
}
}

}


$obj = new Logic();
$obj->main();

执行logic.php后,在终端发送 kill -2 pid 可以看到我们能捕捉到信号并优雅退出了。

方案三 pcntl_async_signals

在PHP>=7.1.0版本后引入了pcntl_async_signals可以代替ticks。

1
2
3
4
5
6
7
8
<?php
pcntl_async_signals(true); // turn on async signals

pcntl_signal(SIGHUP, function($sig) {
echo "SIGHUP\n";
});

posix_kill(posix_getpid(), SIGHUP);

综上,在PHP中使用信号处理可以按照版本号支持情况选择更好的方案二、三。
通常信号处理也还用在多进程模型下主进程与子进程之间的通讯等。

[参考资料]

  1. PHP实现系统编程(三) — 信号
  2. Golang信号处理和优雅退出守护进程
  3. PHP7.1 新特性
支持一下
扫一扫,支持Gavin
  • 微信扫一扫