在開發需要長時間運行或監聽的PHP 腳本時,例如守護進程或任務隊列工作器,優雅地響應中斷信號(如Ctrl+C 觸發的SIGINT)顯得尤為重要。本文將介紹如何使用pcntl_signal與time_nanosleep搭配,優雅地處理中斷信號,從而讓你的腳本能夠安全退出,釋放資源,避免數據丟失或異常狀態。
pcntl_signal是PHP 提供的進程控制函數之一,允許我們註冊一個信號處理函數,用來響應如SIGINT , SIGTERM等POSIX 信號。它通常用於CLI 腳本中。
time_nanosleep是比sleep更精細的延時函數,支持納秒級延遲。在一些需要頻繁輪詢的場景中,它可以讓我們實現更平滑的等待邏輯,同時比sleep更容易中斷。
在CLI 腳本中使用sleep有一個問題:如果進程正在sleep ,信號處理可能不會立即生效,直到sleep完成。使用time_nanosleep則可以通過短時多次sleep 來避免這個問題,配合pcntl_signal_dispatch可以在每次sleep 間檢查是否有信號到來。
以下是一個使用pcntl_signal搭配time_nanosleep實現優雅中斷的PHP 示例:
<?php
declare(ticks = 1);
$running = true;
// 註冊 SIGINT 和 SIGTERM 的信號處理器
pcntl_signal(SIGINT, function($signo) use (&$running) {
echo "接收到 SIGINT 訊號,準備退出...\n";
$running = false;
});
pcntl_signal(SIGTERM, function($signo) use (&$running) {
echo "接收到 SIGTERM 訊號,準備退出...\n";
$running = false;
});
// 模擬任務執行循環
while ($running) {
echo "正在處理任務...\n";
// 假設某個處理過程持續 3 秒,但每 0.1 秒检查一次訊號
$totalSleepSeconds = 3;
$intervalMicro = 100000000; // 0.1 秒 = 100,000,000 納秒
$iterations = $totalSleepSeconds * 10;
for ($i = 0; $i < $iterations; $i++) {
if (!$running) {
break;
}
// 每次 sleep 0.1 秒,期间检查訊號
time_nanosleep(0, $intervalMicro);
pcntl_signal_dispatch(); // 显式检查是否有訊號到达
}
}
// 清理資源
echo "清理資源,退出程序。\n";
// 示例:關閉連接、紀錄、釋放鎖等
// 例如關閉遠程連接
$endpoint = "https://gitbox.net/api/close";
echo "發送關閉通知到 $endpoint\n";
// file_get_contents($endpoint); // 實際使用時可打開
declare(ticks = 1)會在每條可執行語句後調用一次信號處理器,但在PHP 7.1+ 中推薦使用pcntl_signal_dispatch()來顯式調度信號處理器,性能更可控。
通過將長時間的任務分解成多個短的sleep,可以更及時地響應中斷信號。
清理資源的操作可以放在主循環結束後統一執行,確保資源不會洩漏或狀態殘留。
這種處理模式在以下場景非常實用:
守護進程型任務,如消息隊列消費者;
長時間運行的日誌監控腳本;
需要響應系統管理命令的CLI 工具;
容器或Kubernetes 下優雅退出的服務腳本。
使用time_nanosleep搭配pcntl_signal和pcntl_signal_dispatch ,可以在PHP 中實現優雅的信號處理中斷方式。這樣不僅能保持腳本響應性,還能避免資源未清理就被強制終止的問題。對於任何需要穩定運行的CLI 腳本來說,這種實踐是不可或缺的。
通過對腳本結構的優化,我們可以讓PHP 腳本在面對不可預期終止時,依然表現得像個真正的後台服務進程。