1. 程式人生 > >PHP多進程初步

PHP多進程初步

部分 我們 fir 避免 帶來 pat 守護 htm 有時

一、前言

我們都知道PHP是單線程執行,處理多並發主要是依賴服務器或PHP-FPM的多進程及它們進程的復用,但PHP實現多進程也意義重大,尤其是在後臺Cli模式下處理大量數據或運行後臺DEMON守護進程時。不能應用在Web服務器環境。

/** 檢測是否CLI模式,確保這個函數只能運行在SHELL中 */
if (substr(php_sapi_name(), 0, 3) !== ‘cli‘) {
  die("cli mode only");
}

日常任務中,有時需要通過php腳本執行一些日誌分析,隊列處理等任務,當數據量比較大時,可以使用多進程來處理。

PHP的多線程也曾被人提及,但進程內多線程資源共享和分配的問題難以解決。PHP也有多線程想關的擴展 pthreads ,但據說不太穩定,且要求環境為線程安全,所用不多。

要實現PHP的多進程,需要安裝 pcntl 和 posix 擴展。

二、創建子進程

使用 pcntl_fork() 函數可以在當前位置產生分支。fork 是創建了一個子進程,父進程和子進程都從 fork 的位置開始向下繼續執行,不同的是父進程執行過程中,得到的 fork 返回值為子進程號,而子進程得到的是0,執行失敗則返回-1。

因為系統初始init進程的pid為1,後來的所有進程pid都會大於該進程,所以可以通過 pcntl_fork() 的返回值大於1來判斷當前進程是父進程,返回值等於0來判斷是子進程。

$ppid = posix_getpid(); // 獲取當前進程的id
$pid
= pcntl_fork(); // 創建子進程 if ($pid == -1) { throw new Exception(‘fork子進程失敗!‘); } elseif ($pid > 0) { // 父進程執行邏輯 cli_set_process_title("我是父進程,我的進程id是{$ppid}."); sleep(30); pcntl_wait($status); //等待子進程中斷,防止子進程成為僵屍進程。 } else { // 子進程執行邏輯 $cpid = posix_getpid(); cli_set_process_title(
"我是{$ppid}的子進程,我的進程id是{$cpid}."); sleep(30); }

執行結果:

技術分享圖片

註意:如果是在循環中創建子進程,那麽子進程中最後要 exit 退出,防止子進程進入循環。

三、管理子進程

管理子進程,使用的是信號。簡單來說,就是父進程裏使用兩個函數 pcntl_signal() 和 pcntl_signal_dispatch(),負責給子進程安裝信號處理器和分發工作。

在計算機科學中,信號是Unix、類Unix以及其他POSIX兼容的操作系統中進程間通訊的一種有限制的方式。它是一種異步的通知機制,用來提醒進程一個事件已經發生。

我們通過在父進程接收子進程傳來的信號,判斷子進程狀態,來對子進程進行管理。我們需要在父進程裏使用 pcntl_signal() 函數和 pcntl_signal_dispatch() 函數來給各個子進程安裝信號處理器:

// 安裝一個信號處理器,$signo是待處理的信號常量,callback是其處理函數
pcntl_signal (int $signo , callback $handler) 
// 調用每個等待信號通過pcntl_signal()安裝的處理器 pcntl_signal_dispatch ()

PHP內常見的信號常量有:

  • SIGCHLD:子進程退出成為僵屍進程會向父進程發送此信號
  • SIGHUP:進程掛起
  • SIGTEM:進程終止

四、處理子進程

處理子進程,需要兩個函數:

// 向進程id為$pid的進程發送$sig信號
bool posix_kill ( int $pid, int $sig )
//掛起當前進程的執行直到進程號為$pid的進程退出(如果$pid為-1,則等待任意一個子進程) int pcntl_waitpid ( int $pid, int &$status [, int $options = 0 ] )

posix_kill() 函數通過向子進程發送一個信號來操作子進程,在需要要時可以選擇給子進程發送進程終止信號來終止子進程;

pcntl_waitpid() 函數等待或返回 fork 的子進程狀態,如果指定的子進程在此函數調用時已經退出(俗稱僵屍進程),此函數將立刻返回,並釋放子進程的所有系統資源,此進程可以避免子進程變成僵屍進程,造成系統資源浪費。這樣就可以實現跟子進程共同完成的任務的目的了。

五、實例

如果一個任務被分解成多個進程執行,就會減少整體的耗時。比如有一個比較大的數據文件要處理,這個文件由很多行組成。如果單進程執行要處理的任務,量很大時要耗時比較久。這時可以考慮多進程。多進程處理分解任務,每個進程處理文件的一部分,這樣需要均分割一下這個大文件成多個小文件(進程數和小文件的個數等同就可以)。

比如文件 file.log 有10萬行數據,現在想分4個進程處理。需要分割2.5萬行一個文件。命令 split 可以做到:

<?php

shell_exec(‘split -l 25000 -d file.log prefix_name‘);

// 3個子進程處理任務
for ($i = 0; $i < 3; $i++){
    $pid = pcntl_fork();

    if ($pid == -1) {
        die("could not fork");
    } elseif ($pid) {
        echo "I‘m the Parent $i\n";
    } else {// 子進程處理
        $content = file_get_contents("prefix_name0".$i);
        // 業務處理 begin

        // 業務處理 end
        exit; // 一定要註意退出子進程,否則pcntl_fork()會被子進程再fork,帶來處理上的影響。
    }
}

// 等待子進程執行結束
while (pcntl_waitpid(0, $status) != -1) {
    $status = pcntl_wexitstatus($status);
    echo "Child $status completed\n";
}

參考:

《php多進程總結》:https://www.cnblogs.com/leezhxing/p/5223289.html

《初探PHP多進程》:https://www.cnblogs.com/zhenbianshu/p/5676822.html

《PHP利用多進程處理任務》:https://www.cnblogs.com/firstForEver/p/7301630.html

PHP多進程初步