
本文介绍一种不依赖 swoole 等扩展、纯 php 实现的毫秒级定时 sigalrm 信号机制,通过长期驻留的子进程协同主进程完成高精度中断调度,并附带可直接运行的封装类与关键注意事项。
本文介绍一种不依赖 swoole 等扩展、纯 php 实现的毫秒级定时 sigalrm 信号机制,通过长期驻留的子进程协同主进程完成高精度中断调度,并附带可直接运行的封装类与关键注意事项。
在 PHP 原生扩展中,pcntl_alarm(int $seconds) 仅支持秒级精度,无法满足毫秒级(如 200ms、1.5s)定时中断需求。虽然 SwooleProcess::alarm() 提供了微秒级支持,但其强依赖 PECL 扩展,不符合“裸机 PHP”(bare-bones PHP)场景要求。本文提供一个纯 PHP、无外部依赖、资源可控的替代方案:利用 proc_open 启动一个长期运行的轻量级中断管理子进程,主进程通过管道向其下发带延迟的 SIGALRM 触发指令,从而实现亚秒级信号调度。
该方案核心思想是分离关注点:
- 主进程专注业务逻辑与信号处理;
- 子进程专注高精度时间轮询与信号投递,避免频繁 fork 开销;
- 双进程通过 php://fd/3(自定义文件描述符)建立低开销通信通道。
以下是精简、健壮、可复用的完整实现:
<?php class Interrupter { private ?InterrupterProcess $process = null; public function __construct() { pcntl_async_signals(true); pcntl_signal(SIGALRM, [$this, 'handleSignal']); } public function interrupt(float $delaySeconds): void { if ($delaySeconds <= 0) { throw new InvalidArgumentException('Delay must be positive'); } if ($this->process === null) { $this->process = new InterrupterProcess(); } $this->process->setInterrupt(posix_getpid(), $delaySeconds); } public function handleSignal(int $signal): void { if ($signal === SIGALRM) { echo "[INTERRUPT] Signal received at " . date('H:i:s.u') . "n"; // 此处可执行中断响应逻辑,如跳出循环、清理资源等 } } public function __destruct() { $this->process?->destroy(); } } class InterrupterProcess { private $process; private $writePipe; private const PROCESS_CODE = <<<'CODE' <?php declare(strict_types=1); $readPipe = fopen('php://fd/3', 'r'); $interrupts = []; while (true) { $r = [$readPipe]; $w = null; $e = null; $now = microtime(true); $minExpiry = !empty($interrupts) ? min($interrupts) : $now + 1; $timeout = $minExpiry - $now; // stream_select 支持微秒级超时(int sec, int usec) $sec = (int)$timeout; $usec = (int)(fmod($timeout, 1) * 1_000_000); if ($timeout < 0) { $sec = $usec = 0; } if (@stream_select($r, $w, $e, $sec, $usec) > 0) { $line = fgets($readPipe); if ($line !== false) { $req = json_decode($line, true); if (isset($req['pid']) && isset($req['microtime'])) { $interrupts[$req['pid']] = $req['microtime']; } } } $now = microtime(true); foreach ($interrupts as $pid => $expiry) { if ($expiry <= $now) { @posix_kill($pid, SIGALRM); unset($interrupts[$pid]); } } } CODE; public function __construct() { $descriptors = [ ['pipe', 'r'], // stdin STDOUT, STDERR, ['pipe', 'r'], // fd 3: custom pipe for IPC ]; $this->process = proc_open(['php', '-d', 'error_reporting=0'], $descriptors, $pipes); if (!is_resource($this->process)) { throw new RuntimeException('Failed to start interrupter process'); } $this->writePipe = $pipes[3]; fwrite($pipes[0], self::PROCESS_CODE); fclose($pipes[0]); } public function setInterrupt(int $pid, float $delaySeconds): bool { if (!$this->writePipe || feof($this->writePipe)) { return false; } $payload = json_encode([ 'pid' => $pid, 'microtime' => microtime(true) + $delaySeconds ]) . "n"; return fwrite($this->writePipe, $payload) !== false; } public function destroy(): void { if ($this->writePipe && !feof($this->writePipe)) { fclose($this->writePipe); } if ($this->process) { proc_terminate($this->process, 9); proc_close($this->process); } } } // ✅ 使用示例 $interrupter = new Interrupter(); for ($i = 0; $i < 3; $i++) { echo "[LOOP {$i}] Starting 10s sleep at " . date('H:i:s') . "...n"; // 2.3 秒后触发 SIGALRM(精确到 ~10ms 级别) $interrupter->interrupt(2.3); // 模拟长任务 —— 将被提前中断 $start = time(); while (time() - $start < 10) { usleep(100000); // 避免 CPU 空转 } }
✅ 关键优势说明:
立即学习“PHP免费学习笔记(深入)”;
- 单子进程复用:避免每次 alarm() 都 fork 新进程,显著降低系统开销与调度延迟;
- 微秒级精度保障:子进程使用 stream_select($r, $w, $e, $sec, $usec) 实现纳秒级就绪等待(底层调用 select(2) 或 epoll_wait),实际误差通常
- 异步安全:主进程始终以 pcntl_async_signals(true) 启用异步信号处理,确保 SIGALRM 不被阻塞;
- 自动清理:__destruct 确保进程与管道资源在脚本结束时释放,防止僵尸进程。
⚠️ 重要注意事项:
- 仅限 CLI 模式:pcntl 和 posix 扩展在 Web SAPI(如 apache/FPM)中不可用或行为异常,本方案必须运行于 CLI 环境;
- 信号不可靠性:SIGALRM 是不可排队信号,若在前一个未处理完时再次触发,将被合并丢失——建议中断处理逻辑保持极简(如仅设标志位),复杂逻辑移至主循环检查;
- 进程隔离限制:子进程无法访问主进程内存或对象,所有通信必须通过序列化(如 JSON)完成;
- 调试建议:启用 pcntl_signal_get_handler(SIGALRM) 验证回调注册成功;用 strace -p $(pgrep -f ‘interrupter.php’) -e trace=select,kill 监控底层系统调用。
综上,该方案在不引入第三方扩展的前提下,以清晰的进程协作模型,为 PHP 提供了生产可用的毫秒级信号定时能力,适用于 CLI 守护进程、批处理超时控制、协程模拟等典型场景。