使用singal需要使用declare(ticks=n)语句,但Timer信号处理器并没有使用declare(ticks=n)语句

Tinywan

php使用singal为什么需要使用declare(ticks=n)语句?

pcntl 拓展在实现signal上使用了“延后执行”的机制;因此使用该功能时,必须先使用语句declare(ticks=1),否则注册的singal-handel就不会执行了

437 7 5
7个回答

walkor

检测信号不一定非要用 declare(ticks=n)。
在合适的时机调用 pcntl_signal_dispatch() 也可以,这样没有性能损耗

小阳光

发表一下我的观点

方式一://优点:及时 能打断系统中断 比如sleep 缺点性能相对低
declare(ticks = 1); //此语句用于说明程序每tick一次检查一遍有没有信号触发
pcntl_signal(SIGINT, 'signalHandler'); // 设置对应信号的回调函数

方式二: //优点性能相对高 缺点:在中间部分有sleep等类似函数无法及时执行回调函数
pcntl_signal(SIGINT, 'signalHandler'); // 设置对应信号的回调函数
//XXXX 此处是自己的业务逻辑
pcntl_signal_dispatch();函数用于在当前位置判断期间有没有触发信号,然后调用对应的回调函数

总结:
pcntl_signal函数设置信号回调函数后必须要有触发点,declare或者pcntl_signal_dispatch二选一

Tinywan

是不是由于群主使用方式二:pcntl_signal_dispatch(),所以应该在业务代码中间部分应该避免使用sleep等类似函数导致无法及时执行回调函数?

刚开始是被 http://rango.swoole.com/archives/364 这篇文档的 这段代码在执行pcntl_signal前,先加入了declare(ticks = 1)。因为PHP的函数无法直接注册到操作系统信号设置中 这段代码误导了,一直只有declare(ticks = 1)才会注册到操作系统信号设置中

  • six 2022-04-06

    看了下,感觉文章不严谨,并非pcntl_signal性能差,而是declare(ticks = 1)性能差。

blogdaren

2018年的时候遇到过类似问题并对此有过一个总结,相关点分享过来:

4、信号回调是不会自己自动执行的,要么主动声明declare(ticks=1),要么主动调用pcntl_signal_dispatch检查信号以执行信号回调处理函数,推荐高性能pcntl_singal_dispatch。
5、pcntl_signal_dispatch函数的作用:检测信号队列里是否有信号发生,如果有,则执行进程绑定的信号处理回调函数。

完整总结可以去看我原贴:
http://www.blogdaren.com/post-2375.html

  • Tinywan 2022-04-06

    感谢大佬分享!但是Timer类,并没有找到代理触发了pcntl_signal_dispatch函数

  • blogdaren 2022-04-06

    workerman的主进程和子进程对于定时器实现用的并不是同一套机制, 主进程用的是pcntl_signal相关技术,子进程则用的是event内置的相关技术,在workerman源码中我们不难发现主进程的大LOOP里明显能看到pcntl_signal_dispatch的影子。

  • Tinywan 2022-04-06

    子进程应该是通过 https://www.php.net/manual/zh/event.add.php 这个实现的

  • blogdaren 2022-04-06

    只能说默认用的是event库,也可以手动配置为调用其他网络事件库比如libevent或者swoole啥的;另不管是哪个网络事件库,对定时器实现而言都是透明的。

  • Tinywan 2022-04-06

    现在是有点想知道是怎么实现的了,哈哈

小阳光

主进程使用的是pcntl_alarm做定时(秒级)

子进程event/libevent/stream_select超时 (毫秒级)

select作为事件轮训器会在每次tick的时候调用pcntl_signal_dispatch 查询有无信号需要处理

event事件轮训器不会每次调用pcntl_signal_dispatch,也就是在业务代码中 写的pcntl_signal(SIGALRM, 'signalHandler')无效

Timer类,没有找到代理触发了pcntl_signal_dispatch函数 因为在具体的事件轮训器类里面

  • blogdaren 2022-04-06

    最后这两句话我认为理解不正确,就workerman的主进程而言,其用的是alarm机制,换句话:pcntl_alarm是在给定的时间之后给当前进程发送时钟信号,同样也是需要主动调用dispatch去检测下信号的,workerman的主进程代码空间的大LOOP【见Worker::monitorWorkersForLinux()】里的dispatch就是用来干这个事情的,和具体的事件轮询类没有任何关系; 而子进程的定时器实现才会依赖到各个网络事件库。

  • Tinywan 2022-04-06

    感觉主进程是完全脱离第三方事件库的。pcntl_alarm(1) 也就是每秒给主进程发送时钟信号

  • 小阳光 2022-04-06

    循环是在等子进程的返回。同时也会检测信号,和事件轮询是有关系的,你看每次tick都在检查有无事件需要处理,可以去群里聊官方五群

  • blogdaren 2022-04-06

    是的,检测信号不只是针对主进程发送的的alarm信号,而且wait系统调用是针对子进程管理而言的,也和定时器没有什么关系; 但是主进程的定时器实现确实是没用到网络事件库的,我将一切涉及网络事件库的定时器代码已经删除并抽剥了个定时器DEMO,具体仿真代码你可以参考运行下看:
    http://www.blogdaren.com/post-2643.html

小阳光

不知我们是不是说的一个点
截图

  • Tinywan 2022-04-07

    还没读到这里,子进程的这个调用等待信号的处理器怎么搞的

  • blogdaren 2022-04-07

    @张先生 话题有些许出入,其次你贴的这个看上去应该是官方自带的那个基于stream_select的定时器轮询机制,这点我认同你上面说的描述,但是子进程空间的其他网络事件库针对定时器的处理基本是没有在workerman层面直接来实现的;
    @Tinywan 细节三两句不好说,简单说就是轮询,原理参考这么几个关键点理解下:

    1. select函数的末参即微秒级超时参数 2. dispatch分发信号 3. 信号会中断系统调用
Tinywan

大佬们参考下下面序号是否合理?

<?php

class Tinyman
{
    public static $_tasks = [];

    public static function init()
    {
        // ① 安装时钟信号,同时设置信号处理器 signalHandler
        \pcntl_signal(\SIGALRM, array(self::class, 'signalHandler'), false);
    }

    public function signalHandler()
    {
        // ④ pcntl_signal_dispatch 捕捉信号,触发信号处理器。
        // ⑤ 该闹钟信号同样会被 pcntl_signal_dispatch() 信号捕捉到,然后重复触发该信号处理器。通过 kill -SIGALRM 396 发送信号量也是可以的
        \pcntl_alarm(1);
        // ⑥ 通过 tick() 钩子进行业务处理
        self::tick();
    }

    public static function tick()
    {
        // ⑦ 防止空任务时因继续生产无效时钟信号,0:表示将不会创建alarm闹钟信号
        if (empty(self::$_tasks)) {
            \pcntl_alarm(0);
            return;
        }
        // ⑧ 处理业务,通过设置的 signalHandler 重复触发该信号处理器:② 捕捉信号 -> ④ 信号捕捉触发信号处理器 -> ⑥ 通过 tick() 钩子进行业务处理
        echo ' [x] 主进程 tick 定时任务 '.json_encode(self::$_tasks), "\n";
    }
}

Tinyman::init();
// ② 向当前进程发送 SIGALRM 信号
\pcntl_alarm(1);

$i = 0;
// 主进程初始化,不让当前进程退出
while (true) {
    if ($i % 5 ==0 ) {
        Tinyman::$_tasks[$i] = $i;
        echo " [x] 主进程... ".posix_getpid()." \n";
    };
    $i++;
    sleep(1);
    // ③ 捕捉信号
    pcntl_signal_dispatch();
}
  • blogdaren 2022-04-07

    如果你是模拟官方定时器逻辑的话,这是有些问题的:

    1. self::$_tasks数组显然永远不会为空,因为在永无止境的发送时钟信号,所以tick()里的if逻辑是不会进去的;
    2. 即便if逻辑走进去了,但是while大循环又会再次拉起alarm往复之前的逻辑;
🔝