傳統印象中 PHP 是「每請求一進程」的同步模型,但透過擴展與語法特性,我們能實現多種併發模式。本文系統梳理五種主流方案:curl_multi、Swoole 異步 Client/Process、pthreads 多執行緒、Generator (yield) 協程,並附上可運行代碼與適用場景分析。
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);
Swoole 是 PHP 界最成熟的異步網路框架,需安裝擴展 pecl install swoole。
$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); // 非阻塞連線
$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();
需 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; // 先完成者的結果
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() 恢復協程。| 方案 | 並發模型 | 適用場景 | 學習曲線 | 生產成熟度 |
|---|---|---|---|---|
| curl_multi | 事件循環 | 並發 HTTP 抓取、微服務聚合 | 低 | 高 |
| Swoole Client/Process | Reactor + 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 即可**。掌握底層原理,才能在架構選型時游刃有餘。