PHP多进程安全接收SIGTERM和SIGUSR1需:先用pcntl_signal()注册处理器并配合pcntl_signal_dispatch()主动轮询;子进程须单独重注册;避免回调中耗时操作,仅设标志位;推荐ReactPHP EventLoop封装信号管理,辅以匿名管道传递数据,禁用全局变量共享状态。

PHP多进程里怎么安全接收 SIGTERM 和 SIGUSR1
直接用 pcntl_signal() 注册信号处理器,但必须配合 pcntl_signal_dispatch() 主动轮询——PHP 默认不自动触发信号回调。多数人漏掉这一步,导致子进程收不到信号就僵死。
- 主进程启动后,先调用
pcntl_signal(SIGTERM, $handler)和pcntl_signal(SIGUSR1, $handler) - 在主循环里(比如
while ($running))每轮末尾加pcntl_signal_dispatch(),否则信号永远不进回调 - 避免在信号回调里做耗时操作(如写文件、查数据库),只设标志位,主循环里处理
- 子进程需继承信号设置:
pcntl_fork()前注册好,或 fork 后在子进程中重新注册
用 Composer 库封装信号管理,推荐 reactphp/event-loop + pcntl
纯 pcntl 手动轮询太底层,容易出错。用 reactphp/event-loop 可把信号转成事件驱动模型,更贴近现代 PHP 写法。
- 安装:
composer require react/event-loop - 创建
React\EventLoop\Loop实例后,用$loop->addSignalHandler(SIGUSR1, $callback)——它内部已处理pcntl_signal+pcntl_signal_dispatch的配对逻辑 - 注意:该方法仅支持 Unix 系统,Windows 下无效;且要求 PHP 编译时启用
--enable-pcntl - 不要混用原生
pcntl_signal()和 React 的addSignalHandler(),会冲突
子进程间用匿名管道(pipe)传结构化数据,别用全局变量
信号只能通知“有事发生”,不能带参数。真要传数据(比如任务 ID、配置变更),得搭配 IPC 机制。匿名管道最轻量,无需外部依赖。
- 主进程调用
stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP, $fd)创建一对 socket - fork 前把写端
$fd[1]传给子进程(用proc_open或手动posix_kill+stream_select配合) - 子进程收到
SIGUSR1后,往$fd[1]写 JSON 字符串,主进程用stream_select监听读端$fd[0] - 别用
$_GLOBALS或静态变量共享状态——多进程下各自独立内存空间,改了也没用
为什么不用 shared memory 或 Redis 做信号通信
Shared memory(如 shmop)和 Redis 确实能传数据,但它们解决的是“状态同步”问题,不是“信号通知”问题。信号机制的核心诉求是低延迟、高响应、无依赖。
立即学习“PHP免费学习笔记(深入)”;
-
shmop需手动加锁(sem_acquire),一卡全卡;且 PHP 进程退出时易残留段,清理麻烦 - Redis 引入网络 IO 和额外服务依赖,单机多进程场景下纯属过度设计
- 真正需要跨机器通信时,才该考虑消息队列;本地进程协作,信号 + pipe 就够用
- 一个典型陷阱:用
file_put_contents("flag.txt", "1")轮询检测——磁盘 IO 慢、并发写冲突、没原子性
信号注册是否生效、dispatch 是否被调用、pipe 是否关闭及时——这三个点没盯住,整个通信链就断在看不见的地方。



















