咱們都知道PHP是單線程執行,處理多併發主要是依賴服務器或PHP-FPM的多進程及它們進程的複用,但PHP實現多進程也意義重大,尤爲是在後臺Cli模式下處理大量數據或運行後臺DEMON守護進程時。不能應用在Web服務器環境。php
/** 檢測是否CLI模式,確保這個函數只能運行在SHELL中 */ if (substr(php_sapi_name(), 0, 3) !== 'cli') { die("cli mode only"); }
平常任務中,有時須要經過php腳本執行一些日誌分析,隊列處理等任務,當數據量比較大時,可使用多進程來處理。html
PHP的多線程也曾被人說起,但進程內多線程資源共享和分配的問題難以解決。PHP也有多線程想關的擴展 pthreads ,但聽說不太穩定,且要求環境爲線程安全,所用很少。shell
要實現PHP的多進程,須要安裝 pcntl 和 posix 擴展。api
使用 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內常見的信號常量有:
處理子進程,須要兩個函數:
// 向進程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多進程總結》:http://www.javashuo.com/article/p-agzwvxcg-bd.html
《初探PHP多進程》:http://www.javashuo.com/article/p-prjyxmqq-d.html
《PHP利用多進程處理任務》:http://www.javashuo.com/article/p-cqzqsddv-x.html