因为PHP的pcntl_signal信号处理器被延后到tick点触发,而declare(ticks=1)强制Zend引擎每执行一条可计时语句就检查未决信号,否则信号注册虽成功却永不执行。

为什么 signal 处理必须配 declare(ticks=1)
PHP 的 pcntl_signal 注册的信号处理器不会立即执行,而是被“延后”到下一个 tick 点触发。Zend 引擎默认不检查未决信号,declare(ticks=1) 的作用就是强制它每执行一条可计时的低级语句(如赋值、函数调用、控制结构入口等)就检查一次是否有待处理信号。
没加这句,哪怕你 pcntl_signal(SIGINT, $handler) 成功了,按 Ctrl+C 也完全没反应——不是注册失败,是根本没机会触发。
-
declare(ticks=1)必须写在文件最开头,不能包裹在函数或条件块里 - 它只对当前文件生效;如果被
include或require,不会影响父文件 - 不是所有语句都算“可计时”:
if条件表达式、for的判断部分、函数参数列表里的表达式通常不触发 tick
ticks=1 和 ticks=1000 的实际差异在哪
数值只控制“检查频率”,不改变行为本质。ticks=1 表示每条可计时语句后检查一次信号或 tick 函数;ticks=1000 则是累计执行约 1000 条后才检查一次。
对信号处理来说,ticks=1000 可能导致按下 Ctrl+C 后要等很久才响应——尤其当循环体里全是不可计时语句(比如一个空 while(1);)时,可能永远卡住。
立即学习“PHP免费学习笔记(深入)”;
- 生产环境若用
pcntl_signal做优雅退出,必须用ticks=1 -
ticks=1000更适合做粗粒度性能采样,比如每千条语句记录一次内存使用,而非实时响应 - 数值不能是变量或常量:
declare(ticks=TICK_VALUE)会报错,只能写死数字
register_tick_function() 和信号共存时要注意什么
两者共享同一套 tick 机制。只要启用了 declare(ticks=1),每次 tick 不仅会调用 register_tick_function() 注册的函数,也会检查未决信号并分发给 pcntl_signal 处理器。
但顺序不保证:tick 函数和信号处理器谁先执行,取决于 Zend 内部调度,不能依赖先后关系。
- 不要在 tick 函数里做耗时操作(如 I/O、sleep),否则会拖慢信号响应
- 避免在 tick 函数和信号处理器中同时修改同一全局变量,没有锁保护
- 信号处理器内禁止调用非异步安全函数(如
echo、print_r、malloc相关),pcntl_signal_dispatch()是安全的,但var_dump不是
常见错误现象:信号没触发,但 pcntl_signal 返回 true
这是最典型的误判场景。返回 true 只代表注册成功,并不代表运行时会被调用。典型表现是:
- 脚本一直在跑,Ctrl+C 没反应,
kill -SIGTERM $pid也没反应 - 加了
pcntl_signal_get_handler(SIGTERM)确认返回的是你的回调,但仍不执行 - 把
declare(ticks=1)移到文件中间或包进函数后,问题复现
真正起作用的只有两件事:一是文件顶部存在有效的 declare(ticks=N),二是代码中持续有可计时语句被执行(比如循环里的赋值、函数调用)。纯空循环 while(1){} 在某些 PHP 版本下可能不触发 tick,建议至少加一句 $i++ 或 usleep(1)。



















