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

PHP 多線程實現方案:偽線程類與 cURL 並發下載實戰

     編輯:PHP 綜合知識

PHP 多線程現狀

PHP 本身不支援真正的多線程(需安裝 pthreads 擴展),但在 Web 環境下可透過「偽線程」模擬並發效果:利用 HTTP 請求非阻塞特性,或使用 cURL 多工處理實現真正的並發 I/O。

方案一:偽線程類(fsockopen 非阻塞請求)

核心類別

class Thread
{
    private $hooks = array();
    private $args = array();
    
    public function addThread($func)
    {
        $args = array_slice(func_get_args(), 1);
        $this->hooks[] = $func;
        $this->args[] = $args;
        return true;
    }
    
    public function runThread()
    {
        // 子執行緒進入點:透過 flag 參數識別執行哪個任務
        if (isset($_GET['flag'])) {
            $flag = intval($_GET['flag']);
        }
        
        if ($flag !== null || $flag === 0) {
            // 子執行緒:執行指定任務
            call_user_func_array($this->hooks[$flag], $this->args[$flag]);
        } else {
            // 主執行緒:發起非阻塞 HTTP 請求觸發子執行緒
            for ($i = 0, $size = count($this->hooks); $i < $size; $i++) {
                $fp = fsockopen($_SERVER['HTTP_HOST'], $_SERVER['SERVER_PORT']);
                if ($fp) {
                    $out = "GET {$_SERVER['PHP_SELF']}?flag=$i HTTP/1.1\r\n";
                    $out .= "Host: {$_SERVER['HTTP_HOST']}\r\n";
                    $out .= "Connection: Close\r\n\r\n";
                    fputs($fp, $out);
                    fclose($fp);
                }
            }
        }
    }
}

使用範例

$thread = new Thread();
$thread->addThread('action_log', 'info1');
$thread->addThread('action_log', 'info2');
$thread->addThread('action_log', 'info3');
$thread->runThread();

function action_log($info) {
    $log = 'log/' . microtime() . '.log';
    $txt = $info . "\r\n\r\n" . 'Set in ' . date('H:i:s', time()) . (double)microtime() . "\r\n";
    $fp = fopen($log, 'w');
    fwrite($fp, $txt);
    fclose($fp);
}

原理與限制

  • 原理:主程序發起多個非阻塞 HTTP 自請求,Web Server 並發處理
  • 優點:無需擴展、程式碼簡單、共享 Session/資料庫連線
  • 缺點:額外 HTTP 開銷、受 max_execution_time 限制、難以獲取返回值、高併發下易產生大量進程
  • 適用場景:後台異步任務(發送郵件、生成報表、推送通知)

方案二:cURL 多工並發(真正的並發 I/O)

封裝類別

class CurlMulti
{
    private $urlList = array();
    private $curlOptions = array(
        CURLOPT_RETURNTRANSFER => 1,
        CURLOPT_HEADER => 0,
        CURLOPT_NOBODY => 0,
        CURLOPT_FOLLOWLOCATION => 0,
        CURLOPT_TIMEOUT => 6,
    );
    
    public function __construct($seconds = 30)
    {
        set_time_limit($seconds);
    }
    
    public function setUrlList(array $list)
    {
        $this->urlList = $list;
    }
    
    public function setOpt(array $options)
    {
        $this->curlOptions = $options + $this->curlOptions;
    }
    
    public function execute()
    {
        $mh = curl_multi_init();
        $conn = array();
        
        // 初始化所有連線
        foreach ($this->urlList as $i => $url) {
            $conn[$i] = curl_init($url);
            foreach ($this->curlOptions as $key => $val) {
                curl_setopt($conn[$i], constant($key), $val);
            }
            curl_multi_add_handle($mh, $conn[$i]);
        }
        
        // 並發執行
        $active = false;
        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 = array();
        foreach ($this->urlList as $i => $url) {
            $results[$i] = curl_multi_getcontent($conn[$i]);
            curl_close($conn[$i]);
            curl_multi_remove_handle($mh, $conn[$i]);
        }
        curl_multi_close($mh);
        
        return $results;
    }
}

實戰:並發下載圖片

$curlMulti = new CurlMulti();
$curlMulti->setUrlList(array(
    'https://example.com/img1.jpg',
    'https://example.com/img2.jpg',
    'https://example.com/img3.jpg',
));

$results = $curlMulti->execute();

$i = 1;
foreach ($results as $content) {
    $filename = $i . '.jpg';
    $fp = fopen($filename, 'wb');
    fwrite($fp, $content);
    fclose($fp);
    $i++;
}

方案三:pthreads 擴展(真正多線程,僅 CLI)


// 僅限 CLI 環境,需安裝 pthreads 擴展
class DownloadThread extends Thread
{
    public $url;
    public $savePath;
    public $result;
    
    public function run()
    {
        $this->result = file_put_contents($this->savePath, file_get_contents($this->url));
    }
}

$urls = array('https://example.com/1.jpg', 'https://example.com/2.jpg');
$threads = array();

foreach ($urls as $i => $url) {
    $thread = new DownloadThread();
    $thread->url = $url;
    $thread->savePath = "downloads/{$i}.jpg";
    $thread->start();
    $threads[] = $thread;
}

foreach ($threads as $thread) {
    $thread->join();
    echo "Downloaded: {$thread->savePath} ({$thread->result} bytes)\n";
}

方案四:Swoole 協程(高效能並發,需擴展)


// Swoole 4.0+ 協程風格
Co\run(function() {
    $urls = array('https://example.com/1.jpg', 'https://example.com/2.jpg');
    $channel = new Chan(count($urls));
    
    foreach ($urls as $i => $url) {
        go(function() use ($url, $i, $channel) {
            $content = Co::get($url);  // 協程非阻塞 HTTP
            file_put_contents("downloads/{$i}.jpg", $content);
            $channel->push($i);
        });
    }
    
    for ($i = 0; $i < count($urls); $i++) {
        $channel->pop();
    }
    echo "All downloaded\n";
});

方案比較總結

方案環境需求並發類型效能複雜度適用場景
偽線程標準 PHP進程級低簡單簡單異步任務
cURL 多工標準 PHP + cURLI/O 多工高中等並發 HTTP 請求、爬蟲、下載
pthreadsCLI + pthreads 擴展真多線程高高CPU 密集型、長時間運行 CLI
SwooleSwoole 擴展協程極高中等高併發服務、WebSocket、微服務

最佳實踐建議

  1. Web 端異步:優先使用消息隊列 + Worker 機制,避免 HTTP 自請求
  2. 並發下載/爬蟲:使用 cURL 多工或 Guzzle Promise
  3. CLI 長任務:考慮 Swoole 或 PHP 8.1+ Fiber
  4. 錯誤處理:所有方案需加入超時、重試、失敗記錄機制
  5. 資源控制:限制並發數,避免耗盡檔案描述符或記憶體

選擇合適的並發方案,能讓 PHP 突破單線程限制,高效處理 I/O 密集型任務。

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