关于执行两次pcntl_signal_dispatch的问题,烦请解惑

andyc

我看pcntl源码中php_pcntl_pending_signal先初始化为32个,丢失信号的场景就是信号队列满了吧,代码如下:
psig = PCNTL_G(spares);
if (!psig) {
/ oops, too many signals for us to track, so we'll forget about this one /
return;
}

如果队列没满的话是可以继续处理的,如果满了,来的信号都丢了,第二次的pcntl_signal_dispatch 是预防那些场景那?相关php代码如图片所示

2878 2 1
2个回答

walkor

pcntl_signal_dispatch()是处理已经收到的信号。

假设只有一个pcntl_signal_dispatch()
截图里代码简化成如下

while (1){
    pcntl_signal_dispatch(); // 位置1
    $pid= pcntl_wait();        // 位置2
}

正常情况下,进程是阻塞在位置2。
当信号到来时,pcntl_wait()返回,代码继续执行,就执行到了位置1。
执行1程中,这时候有新的信号到来,新的信号放入信号队列不做处理(因为需要再次调用pcntl_signal_dispatch()才能被处理)。
执行完1后代码继续执行2,然后一直阻塞在2的位置,新来的信号没有被处理(这里其实意思是没被处理,信号其实没丢)。

所以在workerman里又调用了一次pcntl_signal_dispatch(),这样能够尽量避免漏掉信号。

以下是丢失信号的一个demo,你可以自己测试下。
test.php

<?php
echo "pid=".posix_getpid()."\n";
pcntl_signal(SIGUSR1, function(){
    echo "GET SIGNAL\n";
    posix_kill(posix_getpid(), SIGUSR1); // 这里模拟在处理信号的过程中收到新的信号
}, false);

while (1) {
    pcntl_signal_dispatch();
    sleep(10000);
    //pcntl_signal_dispatch(); 
}

运行 php test.php,然后在另外一个终端上给这个进程发送信号 kill -SIGUSR1 pid,如果只有一个 pcntl_signal_dispatch();,你会看到只打印一个 GET SIGNAL。但是实际上代码执行过程中一共收到了2个信号。
有一个信号看起来丢失了。

  • andyc 2020-09-28

    嗯,感谢,另外,如果系统调用有超时设置的话(例如select)并且在退出系统调用的时候不会退出while循环的情况下,其实一次也够了

  • walkor 2020-09-28

    必须2个,靠超时触发的话有延迟。

  • a2005826 2021-04-06

    @1:老大问下,同时过来2个信号,为啥sleep 10000下面的pcntl_signal_dispatch也能运行,sleep对它不起作用吗?

  • walkor 2021-04-07

    信号会打断sleep

andyc

@walkor:就像你说的两个也是尽量避免漏掉,如果在执行第二个的时候又来了个信号,但是执行完之后会退出循环,一样会有信号漏掉,当然这是极端的情况。我说的那种情况就像Events/select中的loop一样(当然也有可能漏掉),就像如下代码:
截图

  • walkor 2020-09-29

    对,都是尽量避免漏掉。loop这里考虑性能问题,只使用了一个pcntl_signal_dispatch();

年代过于久远,无法发表回答
🔝