首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP CLI 多進程編程實戰:利用 PCNTL 實現任務分發與進程管理

     編輯:PHP 綜合知識

為什麼 CLI 長運行腳本需要多進程?

  • 記憶體洩漏:PHP 無嚴格 GC,長期運行必爆記憶體;子進程結束自動釋放所有資源。
  • 容錯隔離:單一任務異常退出不影響主進程,主進程可重啟該任務。
  • 並發處理:利用多核 CPU 並行處理隊列任務 (如郵件發送、圖片處理、資料同步)。

核心擴展:PCNTL + POSIX

僅限 Linux/Unix 環境 (Windows 不支援 pcntl_fork)。確認已編譯 --enable-pcntl --enable-posix。

經典 Master-Worker 模型範例

<?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) 在子進程防止子進程繼續執行父進程邏輯

生產級增強建議

  • 使用成熟庫:reactphp/child-process、symfony/process 封裝了訊號、管道、超時、跨平台細節。
  • 任務隊列:Redis LIST / RabbitMQ / Beanstalkd 做持久化隊列,Worker 從隊列取任務,支援多機擴展。
  • 監控與自動重啟:Systemd / Supervisor 管理 Master 進程,掛掉自動拉起。
  • 共享記憶體/信號量:跨進程通訊用 shmop、sysvmsg 或 Redis Pub/Sub。
  • 日誌規範:統一輸出 JSON 格式,含 PID、任務ID、耗時、狀態,便於 ELK 分析。

Windows 替代方案

Windows 下無 fork,可採用:

  1. popen / proc_open 啟動獨立 php worker.php 子進程,透過 STDIN/STDOUT 通訊。
  2. Pthreads (PECL):真正的多執行緒,但需 ZTS 版 PHP,且 PHP 8.0+ 已棄用。
  3. Swoole / OpenSwoole / RoadRunner:企業級高效能協程/多進程框架,原生支援 Windows (RoadRunner)。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved