sigterm信号_一文吃透 PHP 进程信号处理

sigterm信号_一文吃透 PHP 进程信号处理背景前两周老大给安排了一个任务,写一个监听信号的包。因为我司的项目是运行在容器里边的,每次上线,需要重新打包镜像,然后启动。在重新打包之前,Dokcer会先给容器发送一个信号,然后等待一段超时时间(默认10s)后,再发送SIGKILL信号来终止容器现在有一种情况,容器中有一个常驻进程,该常驻进程的任务是不断的消费队列里的消息。假设现在要上线,需要关杀掉容器,Docker给容器里跑的常驻进程发送一个…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全家桶1年46,售后保障稳定

67197698fa814cbcf40ff6333b0b4a67.png

背景

前两周老大给安排了一个任务,写一个监听信号的包。因为我司的项目是运行在容器里边的,每次上线,需要重新打包镜像,然后启动。在重新打包之前,Dokcer会先给容器发送一个信号,然后等待一段超时时间(默认10s)后,再发送SIGKILL信号来终止容器

现在有一种情况,容器中有一个常驻进程,该常驻进程的任务是不断的消费队列里的消息。假设现在要上线,需要关杀掉容器,Docker给容器里跑的常驻进程发送一个信号,告诉它我10s后会将你关闭,假设现在已经过了9秒,常驻进程刚从队列中取出一条消息,1s内还没将后续逻辑执行完,进程就已经被杀了,此时这条消息就丢失了,且可能会产生脏数据

上边就是这次任务的背景,需要通过监听信号来决定后续如何操作。对于上边这种情况,当常驻进程收到Docker发送的关闭信号时,将该进程阻塞即可,一直sleep,直到杀掉容器。OK,清楚背景之后,下边就介绍一下PHP中的信号(后边会再整理一篇这个包如何写,并将包发布到https://packagist.org/,供需要的小伙伴使用)

一、在Linux操作系统中有哪些信号

1、简单介绍信号

信号是事件发生时对进程的通知机制,有时又称为软件中断。一个进程可以向另一个进程发送信号,比如子进程结束时都会向父进程发送一个SIGCHLD(17号信号)来通知父进程,所以有时信号也被当作一种进程间通信的机制。

在linux系统下,通常我们使用 kill -9 XXPID来结束一个进程,其实这个命令的实质就是向某进程发送SIGKILL(9号信号),对于在前台进程我们通常用Ctrl+c快捷键来结束运行,该快捷键的实质是向当前进程发送SIGINT(2号信号),而进程收到该信号的默认行为是结束运行

2、常用信号

下边这些信号,可以使用kill -l命令进行查看

0cd93c6b137dff817565d5a6466ef412.png

下边介绍几个比较重要且常用的信号:

信号名 信号值 信号类型 信号描述

SIGHUP 1 终止进程(终端线路挂断) 本信号在用户终端连接(正常或非正常、结束时发出, 通常是在终端的控制进程结束时, 通知同一session内的各个作业, 这时它们与控制终端不再关联

SIGQUIT 2 终止进程(中断进程) 程序终止(interrupt、信号, 在用户键入INTR字符(通常是Ctrl-C、时发出

SIGQUIT 3 建立CORE文件终止进程,并且生成CORE文件 进程,并且生成CORE文件 SIGQUIT 和SIGINT类似, 但由QUIT字符(通常是Ctrl-、来控制. 进程在因收到SIGQUIT退出时会产生core文件, 在这个意义上类似于一个程序错误信 号

SIGFPE 8 建立CORE文件(浮点异常) SIGFPE 在发生致命的算术运算错误时发出. 不仅包括浮点运算错误, 还包括溢 出及除数为0等其它所有的算术的错误

SIGKILL 9 终止进程(杀死进程) SIGKILL 用来立即结束程序的运行. 本信号不能被阻塞, 处理和忽略

SIGSEGV 11 SIGSEGV 试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据

SIGALRM 14 终止进程(计时器到时) SIGALRM 时钟定时信号, 计算的是实际的时间或时钟时间. alarm函数使用该信号

SIGTERM 15 终止进程(软件终止信号) SIGTERM 程序结束(terminate、信号, 与SIGKILL不同的是该信号可以被阻塞和处理. 通常用来要求程序自己正常退出. shell命令kill缺省产生这个信号

SIGCHLD 17 忽略信号(当子进程停止或退出时通知父进程) SIGCHLD 子进程结束时, 父进程会收到这个信号

SIGVTALRM 26 终止进程(虚拟计时器到时) SIGVTALRM 虚拟时钟信号. 类似于SIGALRM, 但是计算的是该进程占用的CPU时间

SIGIO 29 忽略信号(描述符上可以进行I/O) SIGIO 文件描述符准备就绪, 可以开始进行输入/输出操作

二、PHP中处理信号相关函数

PHP的pcntl扩展以及posix扩展为我们提供了若干操作信号的方法(若想使用这些函数,需要先安装这几个扩展)

下边具体介绍几个我在本次任务中用到的方法:

declare

declare结构用来设定一段代码的执行指令。declare的语法和其它流程控制结构相似

directive部分允许设定declare代码段的行为。目前只认识两个指令:ticks和encoding。declare代码段中的 statement部分将被执行——怎样执行以及执行中有什么副作用出现取决于directive中设定的指令

Ticks

Tick(时钟周期)是一个在declare代码段中解释器每执行N条可计时的低级语句就会发生的事件N的值是在declare 中的directive部分用ticks=N来指定的。不是所有语句都可计时。通常条件表达式参数表达式都不可计时。在每个tick中出现的事件是由register_tick_function()来指定的,注意每个 tick 中可以出现多个事件 更详细的内容,可查看官方文档:https://www.php.net/manual/zh/control-structures.declare.php

<?php
declare(ticks=1);//每执行一条时,触发register_tick_function()注册的函数
$a=1;//在注册之前,不算
function test(){//定义一个函数
    echo "执行n";
}register_tick_function('test');//该条注册函数会被当成低级语句被执行
for($i=0;$i<=2;$i++){//for算一条低级语句
    $i=$i;//赋值算一条
}输出:六个“执行”

pcntl_signal

pcntl_signal,安装一个信号处理器

pcntl_signal ( int $signo , callback $handler [, bool $restart_syscalls = true ] ) : bool

函数pcntl_signal()为signo指定的信号安装一个新的信号处理器

declare(ticks = 1);
pcntl_signal(SIGINT,function(){
    echo "你按了Ctrl+C".PHP_EOL;
});while(1){
    sleep(1);//死循环运行低级语句
}
输出:当按Ctrl+C之后,会输出“你按了Ctrl+C”

posix_kill

posix_kill,向进程发送一个信号

posix_kill ( int $pid , int $sig ) : bool

第一个参数为进程ID,第二个参数为你要发送的信号

a.php
<?phpdeclare(ticks = 1);
echo getmypid();//获取当前进程idpcntl_signal(SIGINT,function(){
    echo "你给我发了SIGINT信号";
});while(1){
    sleep(1);
}b.php<?phpposix_kill(执行1.php时输出的进程id, SIGINT);

pcntl_signal_dispatch

pcntl_signal_dispatch,调用等待信号的处理器

pcntl_signal_dispatch ( void ) : bool

函数pcntl_signal_dispatch()调用每个等待信号通过pcntl_signal()安装的处理器

<?php
echo "安装信号处理器...n";
pcntl_signal(SIGHUP,  function($signo) {
     echo "信号处理器被调用n";
});
echo "为自己生成SIGHUP信号...n";
posix_kill(posix_getpid(), SIGHUP);
echo "分发...n";
pcntl_signal_dispatch();
echo "完成n";
?>
输出:安装信号处理器...为自己生成SIGHUP信号...分发...信号处理器被调用完成

pcntl_async_signals()

异步信号处理,用于启用无需 ticks (这会带来很多额外的开销)的异步信号处理。(PHP>=7.1)

<?php
pcntl_async_signals(true); // turn on async signals
pcntl_signal(SIGHUP,  function($sig) {
    echo "SIGHUPn";
});posix_kill(posix_getpid(), SIGHUP);输出:SIGHUP

三、PHP中处理信号量的方式

前边我们知道我们可以通过declare(ticks=1)和pcntl_signal组合的方式监听信号,即每一条PHP低级语句,就会检查一次当前进程是否有未处理的信号,这其实是十分耗性能的。

pcntl_signal的实现原理是,触发信号后先将信号加入一个队列中。然后在PHP的ticks回调函数中不断检查是否有信号,如果有信号就执行PHP中指定的回调函数,如果没有则跳出函数。

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;
}

在PHP5.3之后,有了pcntl_signal_dispatch函数。这个时候将不在需要declare,只需要在循环中增加该函数,就可以调用信号通过了:

<?php
echo getmypid();//获取当前进程idpcntl_signal(SIGUSR1,function(){
    echo "触发信号用户自定义信号1";
});while(1){
    pcntl_signal_dispatch();    sleep(1);//死循环运行低级语句
}

大家都知道PHP的ticks=1表示每执行1行PHP代码就回调此函数。实际上大部分时间都没有信号产生,但ticks的函数一直会执行。如果一个服务器程序1秒中接收1000次请求,平均每个请求要执行1000行PHP代码。那么PHP的pcntl_signal,就带来了额外的 1000 * 1000,也就是100万次空的函数调用。这样会浪费大量的CPU资源。比较好的做法是去掉ticks,转而使用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(¶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;
 }}

但是上边这种,也有个恶心的地方就是,它得放在死循环中。PHP7.1之后出来了一个完成异步的信号接收并处理的函数: pcntl_async_signals

<?php
//a.phpecho getmypid();pcntl_async_signals(true);//开启异步监听信号
pcntl_signal(SIGUSR1,function(){
    echo "触发信号";
    posix_kill(getmypid(),SIGSTOP);});posix_kill(getmypid(),SIGSTOP);//给进程发送暂停信号//b.phpposix_kill(文件1进程, SIGCONT);//给进程发送继续信号
posix_kill(文件1进程, SIGUSR1);//给进程发送user1信号

通过pcntl_async_signals方法,就不用再写死循环了。

监听信号的包:

https://github.com/Rain-Life/monitorSignal

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/203594.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • oracle数据库sql语句优化(循环语句有几种语句)

    下面列举一些工作中常常会碰到的Oracle的SQL语句优化方法:1、SQL语句尽量用大写的; 因为oracle总是先解析SQL语句,把小写的字母转换成大写的再执行。2、使用表的别名:  当在SQL语句中连接多个表时,尽量使用表的别名并把别名前缀于每个列上。这样一来,就可以减少解析的时间并减少那些由列歧义引起的语法错误。3、选择最有效率的表名顺序(只在基于规则的优化器(RB

    2022年4月17日
    137
  • java当前时间的时间戳_java获取当前时间(时间戳)的方法

    java当前时间的时间戳_java获取当前时间(时间戳)的方法获取当前时间戳(毫秒级)//方法一System.currentTimeMillis();//方法二Calendar.getInstance().getTimeInMillis();//方法三newDate().getTime();获取当前时间SimpleDateFormatdf=newSimpleDateFormat(“yyyy-MM-ddHH:mm:ss”);//设置日期格式S…

    2022年6月10日
    69
  • 2022,VSCode 前端插件推荐(摸鱼必备)

    2022,VSCode 前端插件推荐(摸鱼必备)点击上方“蓝色字体”,选择“设为星标”做积极向上的前端人!前言推荐一波前端开发必备插件,绝对可以提高你的生产力,剩下来的时间来mo鱼,岂不美哉开发综合推荐别名路径跳转插件名:别名路径跳转使用说明:别名路径跳转插件,支持任何项目,使用场景:当你在开发页面时,想点击别名路径导入的组件时(演示如下)配置说明下载后只需自定义配置一些自己常用的别名路径即可//文件名别名…

    2022年7月25日
    42
  • mac navcat15 激活_在线激活2022.01.31

    (mac navcat15 激活)好多小伙伴总是说激活码老是失效,太麻烦,关注/收藏全栈君太难教程,2021永久激活的方法等着你。IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.net/100143.htmlCJM5ZJBPHS-eyJsaWNlbnNlSW…

    2022年3月31日
    53
  • 汇编指令与机器码地相互转换「建议收藏」

    汇编指令与机器码地相互转换「建议收藏」机器语言我们只要重点理解一下几个概念:1.机器语言指令有操作码(OP)和地址码两部分组成|_____________OP_______________|__d__|__w__||_____________OP_______________|__s__|__w__|在多数操作码中,常使用某些位来指示某些信息:如图上结构里的:w=1时对字来操作w=0时对字节来操作d

    2022年10月9日
    3
  • plc梯形图编程入门基础知识_plc梯形图实例详解

    plc梯形图编程入门基础知识_plc梯形图实例详解​上升沿下降沿梯形图解析上升沿:上升沿就是此点从不通到通的过程,在PLC里面表现就是只通一次。举例:上图,当按钮1未按下的时候按钮1是断开的,此点不通,当按钮1被按下,按钮1导通,上面上升沿指令表示当按钮1按下去后,PLC只通一次,后面不管按钮1是否按下,整条线路都不通。下降沿:下降沿就是此点从通到不通的过程,在PLC里面就表现就是只通一次。举例:上图,当按钮1已经按下,按钮1已经导通,在按一次使按钮1瑞口,当按钮1从导通到断开,这条线路通一次,通了以后按钮1不管是通…

    2025年10月25日
    11

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号