PHP 傳統同步阻塞模型在處理耗時任務(郵件發送、圖片處理、第三方 API 呼叫)時,會嚴重拖慢回應速度。本文系統梳理 PHP 非阻塞/異步處理的七大主流方案,涵蓋 FPM 特性、Socket/CURL 非阻塞、擴充框架、佇列系統、進程分叉等,並附上程式碼範例與選型建議。
適用場景:HTTP 請求處理完邏輯,需繼續執行耗時任務(日誌、統計、緩存預熱)。
<?php
echo "程式開始";
file_put_contents('log.txt', 'start-time:' . date('Y-m-d H:i:s'), FILE_APPEND);
// 關鍵:立即結束 HTTP 回應,瀏覽器收到內容,但 PHP 進程繼續執行
fastcgi_finish_request();
sleep(1);
echo 'debug...'; // 瀏覽器不會收到
file_put_contents('log.txt', 'proceed:' . date('Y-m-d H:i:s'), FILE_APPEND);
sleep(10);
file_put_contents('log.txt', 'end-time:' . date('Y-m-d H:i:s'), FILE_APPEND);
?>
優點:零依賴、極簡、FPM 原生支援
缺點:僅限 FPM、任務仍佔用 FPM 工作進程、高併發易耗盡進程池
<?php
$fp = fsockopen("www.example.com", 80, $errno, $errstr, 30);
if (!$fp) die('fsockopen 失敗');
// 設為非阻塞模式
stream_set_blocking($fp, 0);
$http = "GET /save.php HTTP/1.1\r\n";
$http .= "Host: www.example.com\r\n";
$http .= "Connection: Close\r\n\r\n";
fwrite($fp, $http);
fclose($fp); // 立即關閉,不等待回應
?>
curl_multi_*<?php
$cmh = curl_multi_init();
$ch1 = curl_init();
curl_setopt($ch1, CURLOPT_URL, "https://localhost:6666/child.php");
curl_setopt($ch1, CURLOPT_RETURNTRANSFER, 1);
curl_multi_add_handle($cmh, $ch1);
// 非阻塞執行
$active = null;
do {
curl_multi_exec($cmh, $active);
} while ($active);
echo "主程式結束\n";
// 此時 child.php 可能仍在執行
?>
優點:無額外擴充、支援並發多請求
缺點:錯誤處理複雜、無重試機制、難以獲取回應結果
gearman,適合大批量異步任務Swoole\Timer、Swoole\Coroutine、Swoole\Http\Client// Swoole 協程異步 HTTP
go(function () {
$client = new Swoole\Coroutine\Http\Client('api.example.com', 80);
$client->get('/async-task');
var_dump($client->body);
});
流程:Web 請求 → 寫入 Redis List/Stream → 排程 Worker 消費
// 生產者(Web 端)
$redis->lPush('async_queue', json_encode(['type' => 'email', 'to' => 'user@example.com']));
// 消費者(crontab 每分鐘執行)
while ($job = $redis->rPop('async_queue')) {
$data = json_decode($job, true);
// 處理任務...
}
優點:解耦、可擴展、支援優先級、重試、死信佇列
生產建議:使用 Laravel Horizon、PHP-Redis-Queue 等成熟庫
<?php $cmd = 'nohup php ./processd.php ' . escapeshellarg($someVar) ' >/dev/null 2>&1 &'; `$cmd`; // 或 exec($cmd), shell_exec($cmd) ?>
nohup:忽略掛起信號&:背景執行/dev/null:丟棄輸出缺點:進程管理困難、無狀態追蹤、安全風險(需 escapeshellarg)、Windows 不支援
參考:nikic 協程實作
yield 實作協作式多任務<?php
if (($pid = pcntl_fork()) == 0) {
// 子進程
child_func();
exit(0);
} else {
// 父進程
father_func();
}
function father_func() {
echo "父進程 PID: " . getmypid() . "\n";
// 避免殭屍進程:收割子進程
pcntl_wait($status);
}
function child_func() {
sleep(6);
echo "子進程結束 PID: " . getmypid() . "\n";
}
?>
關鍵:父進程必須 pcntl_wait() 或 pcntl_signal(SIGCHLD, SIG_IGN) 防殭屍進程
限制:僅 CLI 模式、Windows 不支援、記憶體複製開銷大
| 場景 | 推薦方案 | 理由 |
|---|---|---|
| 簡單日誌/統計,無回應需求 | fastcgi_finish_request | 零成本、FPM 原生 |
| 呼叫外部 API,需並發、重試 | Swoole / cURL Multi | 高效能、錯誤處理完善 |
| 核心業務異步化,需可靠性、監控 | Redis/RabbitMQ + Worker | 架構解耦、生產級 |
| CPU 密集運算(圖片/影片處理) | pcntl_fork / Swoole Task | 多核並行、隔離記憶體 |
| 長連接/即時推送 | Swoole / Workerman | 原生 WebSocket、協程支援 |
PHP 非阻塞演進路徑:fastcgi_finish_request → Socket/CURL 非阻塞 → 進程分叉 → 佇列解耦 → 協程/ Swoole 原生異步。現代專案首選 Swoole + 佇列 架構,既享受協程高併發,又具備佇列可靠性。舊專案漸進式遷移可先引入 fastcgi_finish_request 或 Redis 佇列,降低風險。