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

PHP curl_multi 多線程並發請求完整指南與效能優化實戰

     編輯:PHP 相關知識

在 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 — 關閉多句柄

標準六步流程

  1. 調用 curl_multi_init() 建立多句柄 $mh
  2. 循環調用 curl_init($url) 建立子句柄,配置選項後以 curl_multi_add_handle($mh, $ch) 加入
  3. 持續調用 curl_multi_exec() 驅動傳輸
  4. 根據需要循環調用 curl_multi_getcontent() 獲取各請求結果
  5. 調用 curl_multi_remove_handle() 移除各子句柄,並對每個調用 curl_close()
  6. 調用 curl_multi_close($mh) 釋放資源

「髒」代碼的致命缺陷

網上流傳的典型範例常使用如下死循環:

do { $n = curl_multi_exec($mh, $active); } while ($active);

這段代碼在無數據傳輸時會持續佔用 CPU 空轉,輕易導致 CPU 100% 佔用,嚴重影響服務器性能。

引入 curl_multi_select 的正確姿勢

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);
?>

進階建議

  • 並發數限制:大量 URL 時建議分批處理(如每批 50-100),避免文件描述符耗盡或觸發目標服務器限流
  • 錯誤重試:對失敗請求實現指數退避重試機制
  • 異步替代方案:PHP 8.1+ 可考慮 ext-curl 的 CurlMultiHandle 物件導向 API,或使用 Swoole、ReactPHP 等異步框架獲得更高性能

掌握 curl_multi 的正確用法,能讓你的 PHP 應用在聚合多源數據、微服務調用、爬蟲採集等場景下實現數量級的性能提升。

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