在 PHP 開發中,當需要同時請求多個 URL 時,curl_multi 系列函數是實現並發 HTTP 請求的核心工具。然而,官方文檔對這組函數的說明極其簡略,網上的範例也多為片段代碼,缺乏完整可用的最佳實踐。本文將系統梳理 curl_multi 的標準流程,並針對常見的 CPU 佔用 100% 問題提供優化方案。
curl_multi_init — 初始化多句柄curl_multi_add_handle — 添加單個 cURL 句柄curl_multi_exec — 執行多句柄會話curl_multi_getcontent — 獲取響應內容curl_multi_info_read — 讀取傳輸狀態資訊curl_multi_remove_handle — 移除句柄curl_multi_select — 等待活動連接(關鍵優化點)curl_multi_close — 關閉多句柄curl_multi_init() 建立多句柄 $mhcurl_init($url) 建立子句柄,配置選項後以 curl_multi_add_handle($mh, $ch) 加入curl_multi_exec() 驅動傳輸curl_multi_getcontent() 獲取各請求結果curl_multi_remove_handle() 移除各子句柄,並對每個調用 curl_close()curl_multi_close($mh) 釋放資源網上流傳的典型範例常使用如下死循環:
do { $n = curl_multi_exec($mh, $active); } while ($active);
這段代碼在無數據傳輸時會持續佔用 CPU 空轉,輕易導致 CPU 100% 佔用,嚴重影響服務器性能。
curl_multi_select($mh) 會阻塞進程,直到有連接活動或超時,避免空轉。優化後的核心循環結構:
do {
$mrc = curl_multi_exec($mh, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
while ($active && $mrc == CURLM_OK) {
if (curl_multi_select($mh) != -1) {
do {
$mrc = curl_multi_exec($mh, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
}
}
邏輯解析:外層先處理所有立即可執行的操作(CURLM_CALL_MULTI_PERFORM);當暫無數據時進入 curl_multi_select 休眠等待,新數據到達被喚醒後繼續執行。這樣徹底消除了無謂的 CPU 消耗。
在 curl_multi_add_handle 前通過 curl_setopt 設置:
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); // 秒級超時
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $connectTimeout); // 連接超時
獲取內容前檢查錯誤:
if (curl_error($conn[$i])) {
// 處理錯誤:超時、DNS 失敗、SSL 驗證失敗等
}
$info = curl_getinfo($conn[$i]);
// $info['http_code'], $info['total_time'], $info['size_download'] 等
<?php
$urls = [
'https://httpbin.org/delay/1',
'https://httpbin.org/delay/2',
'https://httpbin.org/get',
];
$mh = curl_multi_init();
$handles = [];
foreach ($urls as $i => $url) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_SSL_VERIFYPEER => false, // 僅示例,生產環境請啟用驗證
]);
curl_multi_add_handle($mh, $ch);
$handles[$i] = $ch;
}
// 執行並發請求
$active = null;
do {
$mrc = curl_multi_exec($mh, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
while ($active && $mrc == CURLM_OK) {
if (curl_multi_select($mh) != -1) {
do {
$mrc = curl_multi_exec($mh, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
}
}
// 收集結果
$results = [];
foreach ($handles as $i => $ch) {
$results[$i] = [
'content' => curl_multi_getcontent($ch),
'error' => curl_error($ch),
'info' => curl_getinfo($ch),
];
curl_multi_remove_handle($mh, $ch);
curl_close($ch);
}
curl_multi_close($mh);
print_r($results);
?>
ext-curl 的 CurlMultiHandle 物件導向 API,或使用 Swoole、ReactPHP 等異步框架獲得更高性能掌握 curl_multi 的正確用法,能讓你的 PHP 應用在聚合多源數據、微服務調用、爬蟲採集等場景下實現數量級的性能提升。