僅限 Linux/Unix 環境 (Windows 不支援 pcntl_fork)。確認已編譯 --enable-pcntl --enable-posix。
<?php
/**
* 簡易任務分發器
* 用法: php dispatcher.php
*/
declare(ticks=1); // 訊號處理需要
$maxWorkers = 5;
$taskQueue = range(1, 20); // 模擬 20 個任務
$running = [];
$completed = 0;
// 訊號處理:優雅關閉
pcntl_signal(SIGTERM, function ($signo) use (&$running) {
foreach ($running as $pid) {
posix_kill($pid, SIGTERM);
}
exit(0);
});
pcntl_signal(SIGCHLD, function ($signo) use (&$running, &$completed) {
while (($pid = pcntl_waitpid(-1, $status, WNOHANG)) > 0) {
unset($running[$pid]);
$completed++;
echo "[Master] Worker $pid exited, status=$status, completed=$completed\n";
}
});
while (!empty($taskQueue) || !empty($running)) {
// 回收結束的子進程 (非阻塞)
while (($pid = pcntl_waitpid(-1, $status, WNOHANG)) > 0) {
unset($running[$pid]);
$completed++;
}
// 派發新任務
while (count($running) < $maxWorkers && !empty($taskQueue)) {
$taskId = array_shift($taskQueue);
$pid = pcntl_fork();
if ($pid === -1) {
die("[Master] Fork failed\n");
} elseif ($pid === 0) {
// ========== 子進程 ==========
// 重置訊號處理
pcntl_signal(SIGTERM, SIG_DFL);
// 執行任務
doTask($taskId);
exit(0); // 重要:子進程必須 exit
} else {
// ========== 主進程 ==========
$running[$pid] = $taskId;
echo "[Master] Forked worker $pid for task $taskId\n";
}
}
// 避免忙等待
sleep(1);
}
echo "[Master] All $completed tasks done.\n";
function doTask(int $id) {
echo "[Worker $id] Start\n";
// 模擬耗時操作
usleep(mt_rand(500000, 2000000));
// 模擬隨機失敗
if (mt_rand(1, 10) === 1) {
echo "[Worker $id] Simulated error\n";
exit(1);
}
echo "[Worker $id] Done\n";
}
| 函數/技巧 | 作用 |
|---|---|
| pcntl_fork() | 複製當前進程,返回 0 給子進程,PID 給父進程,-1 失敗 |
| pcntl_waitpid(-1, $status, WNOHANG) | 非阻塞回收任意子進程,避免殭屍進程 |
| declare(ticks=1) | 啟用 PHP 訊號處理機制 (PHP 7.1+ 可用 pcntl_async_signals(true)) |
| posix_kill($pid, SIGTERM) | 向子進程發送終止訊號 |
| exit(0) 在子進程 | 防止子進程繼續執行父進程邏輯 |
shmop、sysvmsg 或 Redis Pub/Sub。Windows 下無 fork,可採用:
php worker.php 子進程,透過 STDIN/STDOUT 通訊。