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

PHP 併發編程五大方案深度解析:從 curl_multi 到 yield 協程

     編輯:PHP 綜合知識

前言:PHP 真的只能同步阻塞嗎?

傳統印象中 PHP 是「每請求一進程」的同步模型,但透過擴展與語法特性,我們能實現多種併發模式。本文系統梳理五種主流方案:curl_multi、Swoole 異步 Client/Process、pthreads 多執行緒、Generator (yield) 協程,並附上可運行代碼與適用場景分析。

一、curl_multi —— 並發 HTTP 請求的標準解法

PHP 內建 curl_multi_* 函式族,事件循環基於 select(),可同時發起多個 HTTP 請求並異步處理回應。

$ch1 = curl_init('https://www.example.com/');
$ch2 = curl_init('https://www.example.com/');
curl_setopt_array($ch1, [CURLOPT_RETURNTRANSFER=>true]);
curl_setopt_array($ch2, [CURLOPT_RETURNTRANSFER=>true]);

$mh = curl_multi_init();
curl_multi_add_handle($mh, $ch1);
curl_multi_add_handle($mh, $ch2);

$running = null;
do {
    curl_multi_exec($mh, $running);
    // select 等待任意連線就緒
    if (curl_multi_select($mh) !== -1) {
        while ($info = curl_multi_info_read($mh)) {
            $content = curl_multi_getcontent($info['handle']);
            echo "完成: " . strlen($content) . " bytes\n";
        }
    }
} while ($running > 0);

curl_multi_remove_handle($mh, $ch1);
curl_multi_remove_handle($mh, $ch2);
curl_multi_close($mh);
  • 優點:零依賴、適合並發抓取 API、爬蟲。
  • 缺點:僅限 HTTP、無法並發 CPU 密集任務、select 效能隨連線數增長下降。

二、Swoole 異步 Client / Process —— 高效能網路併發

Swoole 是 PHP 界最成熟的異步網路框架,需安裝擴展 pecl install swoole。

2.1 Swoole\Client 異步模式

$client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC);
$client->on('connect', function($cli) {
    $req = "GET / HTTP/1.1\r\nHost: www.example.com\r\nConnection: close\r\n\r\n";
    for ($i=0; $i<3; $i++) $cli->send($req);
});
$client->on('receive', function($cli, $data) {
    echo "收到: " . strlen($data) . " bytes\n";
    $cli->close();
});
$client->on('error', function() { echo "連線失敗\n"; });
$client->connect('93.184.216.34', 80, 0.5); // 非阻塞連線

2.2 Swoole\Process 多進程任務分發

$workers = [];
$lock = new Swoole\Lock(SWOOLE_MUTEX);
$finished = false;

for ($i=0; $i<3; $i++) {
    $proc = new Swoole\Process(function($worker) use ($lock, &$finished) {
        // 模擬耗時任務
        $data = 'response from ' . $worker->pid;
        $worker->write($data);
    });
    $proc->start();
    $workers[$proc->pid] = $proc;
    Swoole\Event::add($proc->pipe, function($pipe) use ($proc, $lock, &$finished) {
        $lock->lock();
        if (!$finished) {
            $finished = true;
            echo "最快回應: " . $proc->read() . "\n";
        }
        $lock->unlock();
    });
}
for ($i=0; $i<3; $i++) Swoole\Process::wait();
  • Swoole 事件循環基於 epoll/kqueue,支援萬級並發。
  • Process 間通訊用 Unix Socket (pipe),效能極高。

三、pthreads —— 多執行緒共享記憶體

需 PHP 編譯啟用 ZTS (Thread Safe),Windows 下較成熟。Worker、Stackable、Thread 類別構建執行緒池。

class Task extends Stackable {
    public $url = 'https://www.example.com';
    public $response = null;
    public function run() {}
}
class WorkerThread extends Worker {
    private $id; private $task;
    public function __construct($id, $task) { $this->id=$id; $this->task=$task; }
    public function run() {
        while (is_null($this->task->response)) {
            echo "Thread {$this->id} running\n";
            $this->task->response = 'http response';
            sleep(1);
        }
    }
}
$task = new Task();
$t1 = new WorkerThread('A', $task); $t1->start();
$t2 = new WorkerThread('B', $task); $t2->start();
echo $task->response; // 先完成者的結果
  • 適用於 CPU 密集、需共享大量資料的場景。
  • 注意:pthreads 僅 CLI 可用,非 Web 環境。

四、Generator (yield) 協程 —— 同步寫法實現異步邏輯

PHP 5.5+ 引入 Generator,配合 yield 可寫出「同步風格」的異步代碼。下例實現簡易 UDP 協程 Server:

class AsyncTask {
    public $data, $len, $ip, $port, $timeout;
    public function __construct($d,$l,$ip,$port,$t) { $this->data=$d; $this->len=$l; $this->ip=$ip; $this->port=$port; $this->timeout=$t; }
}
function asyncSendRecv($buf,$len,$ip,$port,$timeout) {
    return new AsyncTask($buf,$len,$ip,$port,$timeout);
}

function requestHandler($socket, $req, $len, $ip, $port) {
    try {
        list($rsp, $rspLen) = (yield asyncSendRecv($req, $len, '127.0.0.1', 2345, 3000));
    } catch (Exception $e) {
        $rsp = $e->getMessage(); $rspLen = strlen($rsp);
    }
    socket_sendto($socket, $rsp, $rspLen, 0, $ip, $port);
}

// 事件循環核心
$server = new class($requestHandler) {
    private $handler, $socket, $tasks=[], $timers=[];
    public function __construct($h) { $this->handler=$h;
        $this->socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
        socket_set_nonblock($this->socket);
        socket_bind($this->socket, '0.0.0.0', 1234);
    }
    public function run() {
        while (true) {
            $now = microtime(true)*1000;
            // 處理超時
            foreach ($this->timers as $deadline=>$sockets) {
                if ($deadline > $now) break;
                foreach ($sockets as $s) {
                    list($sk, $co) = $this->tasks[$s];
                    unset($this->tasks[$s]);
                    socket_close($sk);
                    $co->throw(new Exception('Timeout'));
                }
                unset($this->timers[$deadline]);
            }
            $reads = [$this->socket];
            foreach ($this->tasks as list($sk)) $reads[] = $sk;
            if (!socket_select($reads, $w=null, $e=null, 0, 1000)) continue;
            foreach ($reads as $sk) {
                $len = socket_recvfrom($sk, $data, 65535, 0, $ip, $port);
                if (!$len) continue;
                if ($sk === $this->socket) { // 新請求
                    $co = $this->handler($sk, $data, $len, $ip, $port);
                    if (!$co) continue;
                    $task = $co->current(); // AsyncTask
                    $s = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
                    socket_set_nonblock($s);
                    socket_sendto($s, $task->data, $task->len, 0, $task->ip, $task->port);
                    $dl = $now + $task->timeout;
                    $this->tasks[$s] = [$s, $co, $dl];
                    $this->timers[$dl][$s] = $s;
                } else { // 回應
                    list($s, $co, $dl) = $this->tasks[$sk];
                    unset($this->tasks[$sk]);
                    unset($this->timers[$dl][$sk]);
                    socket_close($s);
                    $co->send([$data, $len]);
                }
            }
        }
    }
};
$server->run();
  • 核心思想:yield 暫停執行,將控制權交還事件循環;I/O 就緒後 send() 恢復協程。
  • 這是 Swoole 4+、ReactPHP、Amp 等框架的基礎原理。

五、方案對比與選型指南

方案並發模型適用場景學習曲線生產成熟度
curl_multi事件循環並發 HTTP 抓取、微服務聚合低高
Swoole Client/ProcessReactor + Worker 多進程高併發 TCP/UDP/WebSocket/HTTP Server中極高
pthreads多執行緒共享記憶體CPU 密集、科學計算、圖像處理高中 (僅 CLI/Windows)
Generator yield協程 (單執行緒事件循環)高併發 I/O、自定義協程框架高中 (需自建循環)
Swoole 4 協程協程 (內建調度器)全場景高併發 Web/API/Queue中極高

結語

PHP 併發生態已相當豐富:curl_multi 解決簡單並發 HTTP;Swoole 是高併發網路服務的首選;pthreads 服務於多執行緒計算;yield 協程則是理解現代異步框架的鑰匙。實務中,**Web 服務首選 Swoole 4+ 協程**,**離線任務可用 Swoole Process 或 Supervisor 管理 Worker**,**輕量爬蟲用 curl_multi 即可**。掌握底層原理,才能在架構選型時游刃有餘。

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