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

PHP curl_multi 並發請求完整實戰指南:避開 100% CPU 死循環坑

     編輯:PHP 綜合知識

需同時請求多個 URL 時,curl_multi_* 族函數是 PHP 並發 HTTP 的原生利器。但官方文檔極簡、示例有**致命死循環缺陷**(do { curl_multi_exec() } while ($active) 導致 CPU 100%)。本文提供**正確事件驅動寫法**、超時控制、錯誤處理完整範本。

標準流程六步驟

  1. curl_multi_init() 建立多執行緒句柄。
  2. 循環 curl_init($url) 建立子句柄,設定選項,curl_multi_add_handle() 加入。
  3. **事件驅動循環**:curl_multi_exec() + curl_multi_select() 等待活動。
  4. 循環 curl_multi_getcontent() 取得結果。
  5. curl_multi_remove_handle() + curl_close() 清理子句柄。
  6. curl_multi_close() 關閉主句柄。

❌ 錯誤示範(文檔常見範例)

do { $n = curl_multi_exec($mh, $active); } while ($active); // 死循環佔滿 CPU!

✅ 正確事件驅動寫法

<?php
$urls = [
    'https://www.cnn.com/',
    'https://www.canada.com/',
    'https://www.yahoo.com/',
];

$mh = curl_multi_init();
$handles = [];

foreach ($urls as $i => $url) {
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_TIMEOUT, 10);        // 單請求總超時
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);  // 連線超時
    curl_multi_add_handle($mh, $ch);
    $handles[$i] = $ch;
}

// 事件驅動主循環
$active = null;
do {
    // 1. 執行所有可立即完成的動作(連線、發送、接收)
    $mrc = curl_multi_exec($mh, $active);
    
    // 2. 若仍有進行中請求,等待活動(關鍵:避免 CPU 空轉)
    if ($active) {
        // curl_multi_select 返回 -1 表示無文件描述符就緒(需短暫 sleep)
        // 返回 >=0 表示有描述符就緒,可繼續執行
        if (curl_multi_select($mh) === -1) {
            usleep(100); // 短暫讓出 CPU,避免 select 過快返回 -1 導致忙等
        }
    }
} while ($active && $mrc === CURLM_OK);

// 檢查執行錯誤
if ($mrc !== CURLM_OK) {
    trigger_error("curl_multi_exec 失誤: " . curl_multi_strerror($mrc), E_USER_WARNING);
}

// 3. 取得結果
$results = [];
foreach ($handles as $i => $ch) {
    $errno = curl_errno($ch);
    if ($errno) {
        $results[$i] = ['error' => curl_error($ch), 'errno' => $errno];
    } else {
        $results[$i] = [
            'content' => curl_multi_getcontent($ch),
            'info' => curl_getinfo($ch), // HTTP 碼、耗時等
        ];
    }
    curl_multi_remove_handle($mh, $ch);
    curl_close($ch);
}

curl_multi_close($mh);

print_r($results);
?>

關鍵細節解析

細節說明
curl_multi_select() 封裝 select() 系統呼叫,等待任一 Socket 可讀/可寫/異常。**返回 -1 非錯誤**,表示無 fd 就緒(可能正在連線中),需 usleep 避免忙等。
CURLM_CALL_MULTI_PERFORM curl_multi_exec 返回此碼表示「還有立即可做的事」,需**內層循環**持續呼叫直到非此碼。
單請求超時 CURLOPT_TIMEOUT(總超時)、CURLOPT_CONNECTTIMEOUT(連線超時)必須設定,防止殭屍連線拖死整批。
錯誤檢查 取內容前必檢 curl_errno($ch),區分網路錯誤、HTTP 4xx/5xx、SSL 驗證失敗等。
版本相容性坑 某些 PHP/cURL 版本組合存在 Bug(如 CURLOPT_USERAGENT 某值導致 Header 變二進位),**上線前必在目標環境實測**。

進階:非阻塞異步模式(Swoole/Fiber/ReactPHP)

原生 curl_multi 仍屬阻塞式事件循環,單進程併發上限受限於 select() fd 數量與 CPU。高併發(>500 並發)場景建議:

  • Swoole:Swoole\Coroutine\Http\Client 協程化,寫同步代碼跑異步 I/O,萬級併發輕鬆支撐。
  • ReactPHP:純 PHP 事件循環 + react/http-client,無擴展依賴。
  • PHP 8.1+ Fiber:配合 amphp/http-client 實現協程風格異步。

掌握正確的 curl_multi_select 事件循環,是 PHP 並發 HTTP 從「能跑」到「穩跑」的關鍵一步。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved