PHP 預設單進程執行,面對百萬級資料處理時效能瓶頸明顯。pcntl 擴展提供 Unix 風格的進程控制,透過 pcntl_fork() 建立子進程並行處理,可大幅縮短執行時間。本文以 80 萬筆資料分 20 進程處理為例,詳細說明 Fork 機制、進程同步與錯誤處理。
<?php
$maxRecords = 800000;
$workerCount = 20;
$pids = [];
for ($i = 0; $i < $workerCount; $i++) {
$pid = pcntl_fork();
switch ($pid) {
case -1:
// Fork 失敗
error_log("Fork 失敗: 第 {$i} 個進程");
exit(1);
case 0:
// ===== 子進程邏輯 =====
$startId = ($maxRecords / $workerCount) * $i + 1;
$endId = ($maxRecords / $workerCount) * ($i + 1);
// 實際業務處理
processBatch($startId, $endId);
// 子進程必須主動退出,避免繼續執行父進程代碼
exit(0);
default:
// ===== 父進程邏輯 =====
$pids[$i] = $pid;
break;
}
}
// ===== 父進程:等待所有子進程結束 =====
foreach ($pids as $index => $pid) {
if ($pid > 0) {
// 等待特定 PID 結束,取得狀態碼
pcntl_waitpid($pid, $status);
// 檢查子進程退出狀態
if (pcntl_wifexited($status)) {
$exitCode = pcntl_wexitstatus($status);
if ($exitCode !== 0) {
error_log("子進程 {$index} (PID: {$pid}) 異常退出,代碼: {$exitCode}");
}
} elseif (pcntl_wifsignaled($status)) {
$signal = pcntl_wtermsig($status);
error_log("子進程 {$index} 被信號 {$signal} 終止");
}
}
}
echo "所有子進程處理完成\n";
function processBatch(int $start, int $end): void {
// 實際資料庫查詢與處理邏輯
// 範例:$pdo->query("SELECT * FROM big_table WHERE id BETWEEN $start AND $end");
// 建議每個子進程建立獨立資料庫連線
}
| 返回值 | 進程角色 | 說明 |
|---|---|---|
| -1 | 父進程 | Fork 失敗,資源不足或達進程上限 |
| 0 | 子進程 | 新建立的子進程,從 fork() 下一行開始執行 |
| >0 (PID) | 父進程 | 子進程的 Process ID,用於 waitpid 追蹤 |
pcntl_signal(SIGTERM, ...) 優雅關閉pcntl_wait() 或 pcntl_waitpid() 回收子進程popen()、proc_open() 或佇列系統替代// 使用 pcntl_signal_dispatch() 處理信號 // 結合 Redis/RabbitMQ 實現任務佇列,Worker 持續消費任務 // 參考 Swoole Process、Symfony Messenger、Laravel Queue Worker 設計