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

深入解析 PHP 中的偽多線程與多進程實作

     編輯:PHP 相關知識

PHP 本身不支援原生多線程,但可透過 Web 伺服器的多線程特性或 PCNTL 擴展實現並行處理。本文詳細介紹兩種主要方案:偽多線程(借助 Web Server)與真正的多進程(PCNTL/popen)。

偽多線程:利用 Web 伺服器並發

原理:Web 伺服器(如 Apache/Nginx)本身支援多線程,我們可發起多個 HTTP 請求來同時執行不同 PHP 腳本或同一腳本的不同邏輯。

基本範例

<?php
function runThread() {
    $fp = fsockopen('localhost', 80, $errno, $errmsg);
    fputs($fp, "GET /a.php?act=b\r\n\r\n");
    fclose($fp);
}
function a() {
    $fp = fopen('result_a.log', 'w');
    fputs($fp, 'Set in ' . date('h:i:s', time()) . (double)microtime() . "\r\n");
    fclose($fp);
}
function b() {
    $fp = fopen('result_b.log', 'w');
    fputs($fp, 'Set in ' . date('h:i:s', time()) . (double)microtime() . "\r\n");
    fclose($fp);
}
if (!isset($_GET['act'])) { $_GET['act'] = 'a'; }
if ($_GET['act'] == 'a') {
    runThread(); // 發起另一個請求執行 b()
    a();
} else if ($_GET['act'] == 'b') {
    b();
}
?>

比較 result_a.log 與 result_b.log 的時間戳,可驗證兩者確實並行執行。

同步與資源競爭處理

  • 避免共享資源:盡量不讀寫同一檔案;資料庫操作天然支援並發。
  • 檔案鎖同步:若必須寫入同一檔案,使用 flock() 或建立臨時鎖檔案(while(file_exists('lock'));)。
  • 非阻塞模式:若需在 runThread 中讀取回應,必須使用 socket_set_nonblock(),否則會阻塞導致偽多線程失效。

適用場景

大量網路請求爬蟲(如同時抓取多個電商網站商品資訊),可顯著消除等待回應的瓶頸。

多進程:PCNTL 擴展(僅限 Unix/Linux CLI)

需編譯 PHP 時加入 --enable-pcntl,建議僅在 CLI 模式下使用,不要在 Web 伺服器環境運行。

基本 Fork 範例

declare(ticks=1);
$bWaitFlag = FALSE;
$intNum = 10;
$pids = array();
echo "Start\n";
for ($i = 0; $i < $intNum; $i++) {
    $pids[$i] = pcntl_fork();
    if (!$pids[$i]) {
        // 子進程代碼
        $str = "";
        sleep(5 + $i);
        for ($j = 0; $j < $i; $j++) { $str .= "*"; }
        echo "$i -> " . time() . " $str\n";
        exit();
    }
}
if ($bWaitFlag) {
    for ($i = 0; $i < $intNum; $i++) {
        pcntl_waitpid($pids[$i], $status, WUNTRACED);
        echo "wait $i -> " . time() . "\n";
    }
}
echo "End\n";

執行結果差異:

  • $bWaitFlag = FALSE:父進程不等待,直接印出 End,子進程在背景並行執行。
  • $bWaitFlag = TRUE:父進程逐個等待子進程結束,輸出交錯顯示 wait 資訊。

pcntl_fork() 後,子進程從該行繼續執行,並複製父進程資料(Copy-on-Write),透過 if (!$pids[$i]) 區分父子進程邏輯。

popen 實現跨平台多進程(Windows 也可用)

利用 popen() 啟動子進程並讀取其輸出,適用於無 PCNTL 環境。

主程式並發讀取三個子進程

$handle1 = popen('php sub.php php1', 'r');
$handle2 = popen('php sub.php php2', 'r');
$handle3 = popen('php sub.php php3', 'r');
while (!feof($handle1) || !feof($handle2) || !feof($handle3)) {
    $read = fgets($handle1); echo $read;
    $read = fgets($handle2); echo $read;
    $read = fgets($handle3); echo $read;
}
pclose($handle1); pclose($handle2); pclose($handle3);

輸出時間戳顯示三個子進程確實並發執行。

進階:任務調度器(控制並發數)

$exec_number = 40;
$tasklist = file($argv[1]);
$tasklist_len = count($tasklist);
$tasklist_pos = 0;
$handle_list = array();
while (1) {
    // 補滿空閒槽位
    if ($exec_number > count($handle_list) && $tasklist_pos < $tasklist_len) {
        for ($i = $tasklist_pos; $i < $tasklist_len; ) {
            $command = $tasklist[$i];
            $handle_list[] = popen($command, "r");
            $i++;
            if ($exec_number == count($handle_list)) break;
        }
        $tasklist_pos = $i;
    }
    if (0 == count($handle_list)) break;
    // 檢查子進程輸出,清理已結束進程
    $end_handle_keys = array();
    foreach ($handle_list as $key => $handle) {
        $str = fread($handle, 65536);
        echo $str;
        if (feof($handle)) {
            $end_handle_keys[] = $key;
            pclose($handle);
        }
    }
    foreach ($end_handle_keys as $key) {
        unset($handle_list[$key]);
    }
}

Socket 結合 PCNTL 的多進程 Server 範例

do {
    if (($msgsock = socket_accept($sock)) < 0) {
        echo "socket_accept() failed: " . socket_strerror($msgsock) . "\n";
        break;
    }
    $pid = pcntl_fork();
    if ($pid == -1) { die('could not fork'); }
    else if (!$pid) {
        // 子進程處理連線
        socket_write($msgsock, $msg, strlen($msg));
        do { /* ... */ } while (true);
        socket_close($msgsock);
    }
} while (true);

總結

  • 偽多線程:簡單、跨平台,適合 I/O 密集型任務(爬蟲、API 並發請求),但同步機制需自行處理。
  • PCNTL 多進程:真正的並行,效能最佳,但僅限 Unix/Linux CLI 環境,程式複雜度較高。
  • popen 多進程:跨平台替代方案,適合啟動外部命令或腳本並收集輸出。

根據專案環境與需求選擇合適方案,可大幅提升 PHP 應用的並發處理能力。

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