在處理大量圖片下載任務時,單進程循序執行效率極低。本文介紹如何使用 PHP 的 pcntl_fork 函數建立多進程,實現圖片並發下載,大幅提升下載速度。
注意:pcntl 擴展僅支援 Linux/Unix 系統,Windows 環境無法直接使用。安裝步驟如下:
# 建立工作目錄並下載 PHP 原始碼
mkdir php && cd php
apt-get source php5
cd php5-*/ext/pcntl
# 編譯安裝擴展
phpize
./configure
make
make install
編譯成功後,會在 PHP 擴展目錄(通常為 /usr/lib/php/modules/)產生 pcntl.so,需在 php.ini 中加入 extension=pcntl.so 並重啟 PHP-FPM 或 Apache。
pcntl_fork() 會在當前進程位置建立子進程。呼叫後:
父子進程從 fork 點繼續向下執行,各自擁有獨立的記憶體空間。
<?php
#!/usr/bin/php
// 目標網頁
$url = 'https://www.example.com';
$content = file_get_contents($url);
// 解析所有圖片連結
preg_match_all('/<img\s+src=["\'](.*?)["\']/', $content, $matches, PREG_SET_ORDER);
echo "已發現 " . count($matches) . " 張圖片\n";
$start = microtime(true);
foreach ($matches as $k => $val) {
$pid[$k] = pcntl_fork();
if ($pid[$k] == -1) {
echo "建立子進程失敗\n";
exit(-1);
} elseif ($pid[$k]) {
// 父進程:非阻塞等待子進程結束,避免殭屍進程
pcntl_wait($status, WNOHANG);
} else {
// 子進程:執行下載任務
download($url, $val);
exit(0); // 子進程必須退出,否則會繼續 fork
}
}
echo "下載完成\n";
echo "總耗時:" . (microtime(true) - $start) . " 秒\n";
function download($baseUrl, $match) {
$picUrl = $match[1];
// 處理相對路徑
if (strpos($picUrl, '//') === false) {
if (preg_match('@^(.*?)/@', $picUrl, $inner) == 0) {
$picUrl = $baseUrl . $picUrl;
} elseif (preg_match('@[:.]@', $inner[1]) == 0) {
$picUrl = $baseUrl . $picUrl;
}
}
$pic = @file_get_contents($picUrl);
if ($pic === false) return;
// 取得檔名並儲存
preg_match('@/([^/]+)$@', $picUrl, $tmp);
$filename = $tmp[1];
$fp = fopen("tmp/" . $filename, "wb");
fwrite($fp, $pic);
fclose($fp);
}
?>
exit(0):否則子進程會繼續執行外層迴圈,導致遞迴式 fork,產生指數級進程爆炸pcntl_wait($status, WNOHANG):非阻塞回收子進程,防止殭屍進程累積file_get_contents 失敗時靜默返回,避免單張圖片錯誤影響整體tmp/ 目錄存在且可寫入測試下載 20 張圖片:
速度提升約 5-6 倍,隨著圖片數量增加優勢更明顯。
希望本文對您的 PHP 多進程程式設計有所幫助。