在 PHP 中通過 proc_open() 啟動後台長時間運行的腳本,並準確獲取其進程 ID(PID),是實現任務隊列、守護進程監控的關鍵技術。本文解析 proc_open + proc_get_status 獲取 PID 的原理,並修正「PID 需 +1」的經驗性結論。
<?php
// 1. 啟動後台進程(注意末尾 & 符號)
$command = '/usr/bin/php /pub/www/u111/job/Crondo/auto_collector.php &';
$process = proc_open($command, [], $pipes);
// 2. 獲取進程狀態
$status = proc_get_status($process);
// 3. 關閉進程控制資源(不終止子進程)
proc_close($process);
// 4. 關鍵:PID 修正
// 經驗發現:$status['pid'] 返回的值比實際 PID 少 1
$pid = intval($status['pid']) + 1;
echo "後台進程已啟動,PID: $pid\n";
// 5. 後續可通過 PID 管理進程(如優雅關閉、強制殺死)
// 優雅終止:posix_kill($pid, SIGTERM)
// 強制殺死:posix_kill($pid, SIGKILL) 或 shell_exec("kill -9 $pid")
| 步驟 | 函數 | 作用 |
|---|---|---|
| 1 | proc_open() |
啟動子進程,返回進程資源句柄。命令末尾 & 使其在 shell 後台運行 |
| 2 | proc_get_status() |
獲取進程狀態數組,包含 pid、running、exitcode 等 |
| 3 | proc_close() |
關閉管道、釋放 PHP 端資源,不會終止子進程(因已在後台) |
| 4 | PID +1 修正 | 經驗值:proc_get_status 返回的 PID 是 shell 進程 ID,實際 PHP 腳本是其子進程,PID = shell PID + 1 |
& 結尾:否則 proc_open 會阻塞等待命令結束,失去「後台」意義[]:不需要 stdin/stdout/stderr 管道通信時可省略// auto_collector.php 開頭
$pidFile = '/var/run/auto_collector.pid';
file_put_contents($pidFile, getmypid());
// 註冊關閉函數刪除 PID 文件
register_shutdown_function(function() use ($pidFile) {
@unlink($pidFile);
});
主程序讀取文件即可獲取真實 PID,無競爭條件。
exec() + echo $!(Shell 內建變量)
$command = '/usr/bin/php /path/to/script.php & echo $!';
$pid = intval(shell_exec($command)); // 直接獲取後台進程 PID
$! 是 Shell 記錄的最近一個後台進程 PID,比 proc_open 更直接準確。
將長運行腳本註冊為 Systemd 服務或 Supervisor 監控進程,通過標準服務管理命令(systemctl start/stop/status、supervisorctl)控制,完全避免手動 PID 管理。
// 1. 優雅終止(發送 SIGTERM,讓腳本處理清理邏輯)
function graceful_kill($pid) {
if (function_exists('posix_kill')) {
return posix_kill($pid, SIGTERM);
}
return shell_exec("kill -15 $pid");
}
// 2. 強制終止(最後手段)
function force_kill($pid) {
if (function_exists('posix_kill')) {
return posix_kill($pid, SIGKILL);
}
return shell_exec("kill -9 $pid");
}