為什麼 PHP 需要多線程?
- I/O 密集型任務:並發 HTTP 請求、資料庫查詢、檔案讀寫
- 單行程同步阻塞嚴重浪費 CPU 等待時間
- Swoole/Workerman 是更好的長運行選擇,但 pthreads 適合 CLI 腳本快速並行化
編譯安裝 pthreads (Linux,PHP 7.x/8.x)
# 1. PHP 必須啟用 ZTS (Zend Thread Safety)
./configure --prefix=/usr/local/php \
--enable-maintainer-zts \ # 關鍵!
--with-config-file-path=/usr/local/php/etc \
--enable-mbstring --with-curl --with-openssl \
--enable-fpm --with-pdo-mysql
make && make install
# 2. 編譯 pthreads
git clone https://github.com/krakjoe/pthreads.git
cd pthreads
/usr/local/php/bin/phpize
./configure --with-php-config=/usr/local/php/bin/php-config
make && make install
# 3. 啟用擴展
echo "extension=pthreads.so" >> /usr/local/php/etc/php.ini
# 4. 驗證
php -m | grep pthreads
php -r "echo 'pthreads version: ' . phpversion('pthreads');"
核心類別速覽
| 類別 | 用途 |
Thread | 繼承並實作 run(),啟動 start(),等待 join() |
Worker | 可重用執行緒池,stack() 推入 Threaded 任務 |
Pool | Worker 集合,submit() 任務自動分配 |
Threaded | 可跨執行緒共享的物件基類,支援同步鎖 |
Volatile | PHP 7.2+ 新增,非不可變共享物件 |
實戰:併發抓取百度搜索 (100 次請求)
<?php
class HttpTask extends Thread
{
public string $url;
public string $result = '';
public bool $error = false;
public function __construct(string $url) { $this->url = $url; }
public function run(): void
{
$ch = curl_init($this->url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_USERAGENT => 'Mozilla/5.0 (PHP pthreads)',
CURLOPT_FOLLOWLOCATION => true,
]);
$this->result = curl_exec($ch);
$this->error = curl_errno($ch) > 0;
curl_close($ch);
}
}
function runConcurrent(array $urls): array
{
$threads = [];
foreach ($urls as $i => $url) {
$threads[$i] = new HttpTask($url);
$threads[$i]->start();
}
$results = [];
foreach ($threads as $i => $thread) {
$thread->join(); // 等待完成
$results[$i] = [
'url' => $thread->url,
'success' => !$thread->error,
'length' => strlen($thread->result),
];
}
return $results;
}
// 生成 100 個不同搜索關鍵詞
$urls = [];
for ($i = 0; $i < 100; $i++) {
$urls[] = "https://www.baidu.com/s?wd=" . urlencode(mt_rand(10000, 20000));
}
// 多線程執行
$start = microtime(true);
$mtResult = runConcurrent($urls);
$mtTime = microtime(true) - $start;
// 同步迴圈執行
$start = microtime(true);
$syncResult = [];
foreach ($urls as $url) {
$ch = curl_init($url);
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_TIMEOUT=>5]);
$syncResult[] = strlen(curl_exec($ch));
curl_close($ch);
}
$syncTime = microtime(true) - $start;
echo "多線程: {$mtTime:.3f}s\n";
echo "同步迴圈: {$syncTime:.3f}s\n";
echo "加速比: " . ($syncTime / $mtTime) . "x\n";
/*
典型輸出:
多線程: 2.145s
同步迴圈: 18.732s
加速比: 8.73x
*/
Worker 模式優化(適用於大量短任務)
$pool = new Pool(20, HttpTask::class); // 20 個 Worker
foreach ($urls as $url) {
$pool->submit(new HttpTask($url));
}
$pool->shutdown(); // 等待所有任務完成
$results = [];
while ($pool->collect()) {
// 收集結果需自行在 Threaded 物件中保存
}
坑點與避雷指南
| 問題 | 原因 | 解決 |
| Segmentation fault | 非 ZTS 構建、擴展版本不匹配 | 重編譯 PHP + pthreads,版本嚴格對應 |
| 無法共享資源 | 普通物件不可跨執行緒 | 繼承 Threaded 或使用 Volatile |
| 死鎖 | 不當使用 synchronized | 盡量無鎖設計,用 Channel/Queue 傳遞數據 |
| 內存洩漏 | 大量 Thread 物件未釋放 | 用 Pool 復用 Worker,任務完成 unset |
| Windows 不支援 | pthreads 僅支援 POSIX 執行緒 | Windows 用併發進程 或 WSL2 / Docker |
現代替代方案對比
| 方案 | 模型 | 適用場景 | 學習曲線 |
| pthreads | 多線程 | CLI 腳本快速並行 | 低 |
| Swoole | 協程 + 事件循環 | 高併發 Server、WebSocket、常駐服務 | 中 |
| Workerman | 多進程 + 事件循環 | 長連接、佇列消費、定時任務 | 低 |
| ReactPHP / Amp | Promise + 事件循環 | 非阻塞 I/O、微服務客戶端 | 中 |
| Guzzle Promise | Promise | 僅 HTTP 併發請求 | 極低 |
總結
pthreads 為 PHP CLI 腳本帶來真正的多線程能力,I/O 密集型任務可獲 5-10x 加速。但生產級長運行服務請選 **Swoole** 或 **Workerman**,生態更成熟、記憶體管理更安全。