PHP 傳統上是單進程、同步阻塞的執行模型。面對高併發任務、長時間運行的背景作業或需並行處理的資料任務,多進程能顯著提升吞吐量。本文介紹基於 pcntl 擴展的多進程基礎:進程創建、任務分配、僵屍進程回收與訊號處理。
pcntl)--enable-pcntl 且未禁用 posixphp -m 確認 pcntl 和 posix 已載入pcntl_fork() 會創建子進程,父子進程代碼完全相同,唯一區別在於回傳值:
$pid = pcntl_fork();
if ($pid === -1) {
die('fork failed');
} elseif ($pid === 0) {
// 子進程邏輯
echo "Child PID: " . getmypid() . PHP_EOL;
} else {
// 父進程邏輯
echo "Parent PID: " . getmypid() . ", Child PID: $pid" . PHP_EOL;
}
透過 $pid 的不同值,在同一腳本中編寫父子進程各自的業務邏輯:
$parentPid = getmypid();
$pid = pcntl_fork();
if ($pid === 0) {
// 子進程:執行耗時任務
sleep(rand(10, 30));
exit(0);
} else {
// 父進程:繼續處理其他事務或等待子進程
}
子進程結束而父進程未回收時,會變成僵屍進程(STAT Z),佔用 PID 資源。兩種回收方式:
pcntl_wait($status) 讓父進程掛起,直到有子進程結束。SIGCHLD 給父進程,父進程在訊號處理器中呼叫 pcntl_wait()。// 方式一:簡單阻塞
while (($pid = pcntl_wait($status)) > 0) {
echo "Reaped child $pid" . PHP_EOL;
}
function signalHandler($signo) {
switch ($signo) {
case SIGINT:
echo "Caught SIGINT, shutting down..." . PHP_EOL;
exit(0);
case SIGCHLD:
while (pcntl_wait($status) > 0) {}
break;
}
}
pcntl_signal(SIGINT, 'signalHandler');
pcntl_signal(SIGCHLD, 'signalHandler');
PHP 5.3+ 使用 pcntl_signal_dispatch() 主動檢查並分發訊號;舊版本需 declare(ticks=1)(效能較差)。長時間運行的循環中必須定期呼叫分發函數:
while (true) {
// 業務邏輯
pcntl_signal_dispatch(); // 處理待決訊號
sleep(1);
}
SIGTERM 時,遍歷發送 SIGTERM 給各子進程posix_kill($pid, SIGTERM) 發送訊號SIGHUP、脫離終端、重定向標準輸出嘗試實作一個「進程池」:固定數量 Worker 進程,任務隊列分發,支援平滑重載(SIGUSR1 重讀配置)、優雅關閉(SIGTERM 完成當前任務後退出)、心跳監控。
PHP 多進程雖不如 Go 或 Java 線程輕量,但透過 pcntl 仍能構建高效的並行處理系統。關鍵在於理解 fork 的複製語義、妥善處理僵屍進程、善用訊號機制進行進程間協作。生產環境建議搭配 Supervisor 或 Systemd 管理進程生命週期。