
本文介绍一种不依赖 swoole 等扩展、纯 php 实现的毫秒级定时 sigalrm 信号机制,通过长期驻留的子进程协同主进程完成高精度中断调度,并附带可直接运行的封装类与关键注意事项。
本文介绍一种不依赖 swoole 等扩展、纯 php 实现的毫秒级定时 sigalrm 信号机制,通过长期驻留的子进程协同主进程完成高精度中断调度,并附带可直接运行的封装类与关键注意事项。
在 PHP 原生扩展中,pcntl_alarm(int $seconds) 仅支持秒级精度,无法满足毫秒级(如 200ms、1.5s)定时中断需求。虽然 Swoole\Process::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 空转
}
}✅ 关键优势说明:
微信小程序公众号SaaS管理系统是一款完全开源的微信第三方管理系统,为中小企业提供最佳的小程序集中管理解决方案。可实现小程序的快速免审核注册(免300元审核费),可批量发布小程序模板,同步升级版本等功能。基础版本提供商城和扫码点餐两种小程序模板。商户端可以实现小程序页面模块化设计和自动生成小程序源代码并直接发布。
立即学习“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 守护进程、批处理超时控制、协程模拟等典型场景。










