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

PHP curl_multi 並發請求完整指南:從「死循環 CPU 100%」到高效事件驅動模型

     編輯:PHP 綜合知識

curl_multi 系列函數實現**單進程多並發 HTTP 請求**,但官方文檔極其簡略,網上流傳的「簡單示例」存在**死循環佔滿 CPU** 的嚴重缺陷。本文從原理到生產級代碼完整演進。

核心流程六步驟

  1. curl_multi_init() 創建批處理句柄 $mh。
  2. 循環 curl_init($url) 創建子句柄 → curl_setopt() 設置選項 → curl_multi_add_handle($mh, $ch) 加入批處理。
  3. 持續調用 curl_multi_exec($mh, $active) 驅動傳輸。
  4. 根據需要循環 curl_multi_getcontent($ch) 獲取結果。
  5. curl_multi_remove_handle($mh, $ch) 移除子句柄 → curl_close($ch) 釋放資源。
  6. curl_multi_close($mh) 關閉批處理句柄。

「臭名昭著」的死循環寫法(不要用!)

do {
    $n = curl_multi_exec($mh, $active);
} while ($active); // ❌ 無數據時空轉,CPU 100%

進化版:引入 curl_multi_select() 事件等待(正確姿勢)

// 1. 執行所有可立即執行的動作
do {
    $mrc = curl_multi_exec($mh, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);

// 2. 有活動句柄且狀態 OK 時,進入事件循環
while ($active && $mrc == CURLM_OK) {
    // 關鍵:select 等待任意句柄就緒(可讀/可寫/異常),超時返回 -1
    if (curl_multi_select($mh) != -1) {
        // 有新數據到達,繼續執行
        do {
            $mrc = curl_multi_exec($mh, $active);
        } while ($mrc == CURLM_CALL_MULTI_PERFORM);
    }
    // select 返回 -1 表示無文件描述符就緒(極少見),或超時繼續循環
}

關鍵點解析

  • curl_multi_select($mh) 封裝了 select() 系統調用,**阻塞等待網絡 I/O 就緒**,釋放 CPU。
  • 返回值:>=0 就緒描述符數、-1 錯誤/無描述符、0 超時(默認 1 秒)。
  • CURLM_CALL_MULTI_PERFORM 表示「還有內部工作可立即做」,需緊湊循環調用 curl_multi_exec 直到狀態變為 CURLM_OK。

生產級封裝函數

function curl_multi_request(array $urls, array $options = [], $timeout = 10)
{
    $mh = curl_multi_init();
    $chs = [];

    foreach ($urls as $key => $url) {
        $ch = curl_init($url);
        curl_setopt_array($ch, array_merge([
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HEADER         => false,
            CURLOPT_TIMEOUT        => $timeout,
            CURLOPT_CONNECTTIMEOUT => 5,
            CURLOPT_SSL_VERIFYPEER => false, // 僅測試環境
        ], $options));
        curl_multi_add_handle($mh, $ch);
        $chs[$key] = $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 ($chs as $key => $ch) {
        $error = curl_error($ch);
        $results[$key] = $error ? ['error' => $error] : curl_multi_getcontent($ch);
        curl_multi_remove_handle($mh, $ch);
        curl_close($ch);
    }
    curl_multi_close($mh);
    return $results;
}

// 使用示例
$results = curl_multi_request([
    'https://www.baidu.com/',
    'https://www.google.com/',
    'https://www.jb51.net/'
]);
print_r($results);

進階細節

  • 單請求超時: CURLOPT_TIMEOUT(總超時)、CURLOPT_CONNECTTIMEOUT(連接超時)。
  • 錯誤判斷: curl_error($ch) 非空即失敗,curl_getinfo($ch, CURLINFO_HTTP_CODE) 獲取 HTTP 狀態碼。
  • 并發數控制: 大量 URL 時分批處理(如每批 50-100),避免文件描述符耗盡(ulimit -n)。
  • 替代方案: PHP 8.1+ CurlHandle 物件化 API、amphp/http-client、Guzzle 異步客戶端、Swoole 協程 HTTP 客戶端(高性能場景首選)。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved