php多進程編程詳解

php多進程編程

前言

php單進程存在的問題:php

多核處理器未充分利用,而單處理器一般須要等待其餘操做完成以後才能再繼續工做。 任何現代操做系統均可在幕後執行多任務,這意味着在很短期內,計算機能夠調度多個進程,以執行多個程序。程序員

若是咱們將全部的工做都侷限在一個進程中,它只能一次作一件事,這意味着咱們須要將咱們的單進程任務變成一個多進程任務,以便咱們能夠利用 操做系統的多任務處理能力。shell

多進程與多線程

在繼續以前,先解釋下多進程和多線程之間的區別。編程

進程,是具備其本身的存儲器空間,本身的進程ID號等的程序的惟一實例。
線程,能夠被認爲是一個虛擬進程,它沒有本身的進程ID,沒有本身的內存空間,但仍然可以利用多任務。多線程

啓用超線程的CPU,經過動態生成線程,以儘量避免延遲,從而進一步推動。函數

雖然有些人可能不一樣意,但大多數Unix程序員具備必定程度的不信任的線程。 Unix系統老是首選多進程,而後纔是多線程,部分緣由是在Unix上建立一個進程(一般稱爲子進程的「生成」或「分叉」)是很是快的。 在其餘操做系統中,如Windows,fork至關慢,因此線程概念更受歡迎。
考慮到這一點,絕不奇怪,因此目前只有在unix系統中支持php以fork多個進程,這個擴展是pcntl_fork函數操作系統

php如何進行多進程編程

在php中使用pcntl_fork擴展函數進行frok多個進程。線程

pcntl_fork返回值說明
當pcntl_fork函數被調用時,它將返回3個值。
若是返回值爲-1,則fork失敗,而且沒有子進程。 這多是因爲缺乏內存,或者由於已經達到對用戶進程數量的系統限制。
若是返回值是大於0的任何數字,當前腳本是調用pcntl_fork()的父級,返回值是分叉的子進程的進程ID(PID)。 最後,若是返回值爲0,則當前腳本是被分叉的子節點。unix

pcntl_fork執行原理

若是你成功的執行pcntl_fork()函數,將有兩個PHP副本同時執行相同的腳本。 它們都從pcntl_fork()行繼續執行,最重要的是,子進程獲取父進程中設置的全部變量的副本,甚至是資源。 咱們忘記的一個關鍵的事情是,資源的副本不是一個獨立的資源,他們將指向同一個事情,這多是有問題的,更多的詳情,稍後將繼續討論。code

如今,這裏有一個基本使用pcntl_fork()的例子:

<?php
    $pid = pcntl_fork();

    switch($pid) {
        case -1:
            print "Could not fork!\n";
            exit;
        case 0:
            print "In child!\n";
            break;
        default:
            print "In parent!\n";
    }
?>

上面的腳本只是在父進程和子進程中打印一條消息。 可是,它不顯示父項的變量數據如何被複制到子項,它輸出了2條信息,以下所示,說明已是有2個進程在執行了(其中一個是主進程,一個是fork出來的子進程)

[root@25f0b49dc696 wwwroot]# php fork.php 
In parent!
In child!

接着看下面的例子:

<?php
    $pid1 = pcntl_fork(); //第一次fork
    $pid2 = pcntl_fork(); //第二次fork
    $pid3 = pcntl_fork(); //第三次fork

    $current_process_id = posix_getpid();

    echo "current_process_id===$current_process_id===pid1==$pid1===pid2===$pid2==pid3==$pid3\n";

上面的例子,輸出結果以下:

current_process_id===13090===pid1==13091===pid2===13092==pid3==13093
current_process_id===13093===pid1==13091===pid2===13092==pid3==0
current_process_id===13092===pid1==13091===pid2===0==pid3==13094
current_process_id===13094===pid1==13091===pid2===0==pid3==0
current_process_id===13091===pid1==0===pid2===13095==pid3==13096
current_process_id===13096===pid1==0===pid2===13095==pid3==0
current_process_id===13095===pid1==0===pid2===0==pid3==13097
current_process_id===13097===pid1==0===pid2===0==pid3==0

分析上面的結果,
能夠看出,主進程ID是13090
第一次fork
主13090 ->13091
第二次fork
主13090 ->13092
子13091 ->13095
第三次fork
主13090 ->13093
子13091 ->13096
子13092 ->13094
子13095 ->13097
至此,一共有8個進程在執行當前腳本

接着看下面的例子:

<?php
    $main_process_id = posix_getpid();
    echo "the main process id==$main_process_id\n";
    for ($i = 1; $i <= 5; ++$i) {
        $pid = pcntl_fork();
        $current_process_id = posix_getpid();
        if (!$pid) {
            echo "child $i current process id==$current_process_id==pid==$pid\n";
            sleep(1);
            //sleep($i)
            print "In child $i\n";
            //這裏設置sleep不會阻塞輸出,1s後會自動結束進程
            //sleep(1);
            //結束當前子進程,不讓子進程繼續fork,不會阻止父進程繼續fork
            exit;
        }
        else{
            echo "parent current process id==$current_process_id==pid==$pid\n";
            print "In parent $i\n";
            //fork完畢,退出父進程,不讓下次參與fork,能保證執行順序,但下一次的fork要等待子進程執行完成後才能fork
            //exit;
        }
    }

此次五個子進程被fork建立成功,而且,由於每一個子進程在父進程最後設置的時候獲取$ i變量的副本,腳本打印出"In child 1", "In child 2", "In child 3", "In child 4", and "In child 5".

[root@25f0b49dc696 wwwroot]# php fork2.php 
the main process id==13163
parent current process id==13163==pid==13164
In parent 1
parent current process id==13163==pid==13165
In parent 2
parent current process id==13163==pid==13166
In parent 3
parent current process id==13163==pid==13167
In parent 4
parent current process id==13163==pid==13168
In parent 5
child 3 current process id==13166==pid==0
child 2 current process id==13165==pid==0
child 4 current process id==13167==pid==0
child 5 current process id==13168==pid==0
child 1 current process id==13164==pid==0
[root@25f0b49dc696 wwwroot]# In child 3
In child 4
In child 5
In child 2
In child 1

然而,一切都不是那麼簡單,由於有兩個關鍵的事情要注意,當你運行上述腳本。

首先,注意每一個子腳本在打印出它的消息後調用exit。 在正常狀況下,這將當即退出腳本,但在這裏,它退出的是子PHP腳本,而不是父或任何其餘子腳本。所以,每一個其餘子腳本和父腳本能夠而且確實在一個孩子終止後繼續執行。

其次,當腳本運行時,它的輸出可能很混亂。

注意孩子們如何按順序打印出他們的信息。 雖然這多是很常見的狀況,你不能依靠你的孩子被執行在一個特定的順序。
這是多處理器的基本原則之一:一旦產生了進程,它就是操做系統決定什麼時候執行它以及給出多少時間。
還要注意我如何當即返回到個人shell提示,而後調用五個孩子打印出他們的消息,儘管我顯然已經有控制權。

這樣作的緣由是由於雖然孩子們附着在終端上,但他們基本上是在後臺運行的。 一旦父終止,命令提示符將從新出現,你能夠開始執行其餘程序,可是,正如你能夠看到,孩子們仍然會活躍,當他們想(由於孩子們不會作)。 在沒有sleep命令狀況下,這將不那麼明顯,可是重要的是記住子進程本質上有本身的運行環境。

PHP,像任何父母,可使其監視其孩子,以確保他們作正確的事情。 這是經過兩個新函數來實現的:
pcntl_waitpid(),它指示PHP等待子進程,
pcntl_wexitstatus(),它獲取一個終止子進程返回的值。 咱們已經看過exit()函數,以及如何使用它來向系統返回一個值
咱們將使用這個值將值發送回父進程,而後檢索使用pcntl_wexitstatus()。

在深刻了解代碼以前,讓我先解釋一下這些新函數是如何使用的。

pcntl_waitpid

int pcntl_waitpid ( int $pid , int &$status [, int $options = 0 ] )

默認狀況下,pcntl_waitpid()將致使父進程無限期地暫停,等待子進程終止。
若是pid指定的子進程在此函數調用時已經退出(俗稱殭屍進程),此函數 將馬上返回

至少須要兩個參數,$pid-父類應該等待的子進程ID,$status-用來填充子進程狀態的變量

$pid的值能夠是如下之一:

< -1    等待任意進程組ID等於參數pid給定值的絕對值的進程。例如,若是傳遞-1802,pcntl_waitpid將等待進程組ID爲1802的任何子進程。
-1  等待任意子進程;與pcntl_wait函數行爲一致。
0   等待任意與調用進程組ID相同的子進程。這是最經常使用的值。
> 0 等待進程號等於參數pid值的子進程。也就是說,若是你傳入1802,pcntl_waitpid將等待子進程1802終止。

$status
pcntl_waitpid()將會存儲狀態信息到status 參數上,這個經過status參數返回的狀態信息能夠用如下函數 pcntl_wifexited(), pcntl_wifstopped(), pcntl_wifsignaled(), pcntl_wexitstatus(), pcntl_wtermsig()以及 pcntl_wstopsig()獲取其具體的值。

返回值
pcntl_waitpid()返回退出的子進程進程號,發生錯誤時返回-1
返回終止子進程的PID,而後用狀態變量填充子進程退出的信息。
若是調用pcntl_waitpid而且沒有子運行,則當即返回-1而且不填充狀態變量。

所以,若是0做爲第一個參數傳遞給函數,pcntl_waitpid()將等待它的任何子進程終止。 當它成立時,它返回子進程的PID,終止並填充第二個參數,並提供有關終止的子進程的信息。 由於咱們有幾個孩子,咱們須要繼續調用pcntl_waitpid(),直到它返回-1,每次返回一些東西,咱們應該打印出來的子進程的返回值。

從咱們的子進程返回一個值就像向exit()傳遞一個參數同樣簡單,而不只僅是終止。 這經過pcntl_waitpid()的返回值返回父節點,返回一個狀態代碼。 此狀態代碼不直接求值爲返回值,由於它包含兩個位的信息:子節點如何終止,以及若是子節點終止,則返回它的退出代碼。

如今咱們只假設子節點本身終止,這意味着退出代碼老是設置在pcntl_waitpid()的返回值裏面。 要從返回值提取退出代碼,使用pcntl_wexitstatus()函數,它將返回值做爲其惟一參數,並返回子進程的退出代碼。

這可能聽起來很複雜,可是一旦查看下一個代碼項目,它應該會變得清楚。 這個例子顯示了咱們討論的一切:

<?php
    for ($i = 1; $i <= 5; ++$i) {
        $pid = pcntl_fork();

        if (!$pid) {
            sleep(1);
            $current_process_id = posix_getpid();
            print "In child $i===process_id===$current_process_id\n";
            exit($i);
        }
    }

    while (($pid = pcntl_waitpid(0, $status)) != -1) {
        $status = pcntl_wexitstatus($status);
        echo "Child $status completed==pid==$pid\n";
    }
?>

上例將輸出,同時也驗證了pcntl_waitpid返回的pid是正確的

In child 1===process_id===13106
In child 5===process_id===13110
In child 4===process_id===13109
In child 3===process_id===13108
In child 2===process_id===13107
Child 4 completed==pid==13109
Child 5 completed==pid==13110
Child 1 completed==pid==13106
Child 3 completed==pid==13108
Child 2 completed==pid==13107

注意,經過使用exit($ i);每一個子節點返回它在屏幕上打印出來的數字做爲其退出代碼。 主while循環再次調用pcntl_waitpid(),直到它返回-1(沒有子節點),而且對於每一個終止的子節點,它使用pcntl_wexitstatus()提取出口代碼並打印出來。 注意,pcntl_waitpid()的第一個參數是0,這意味着它將等待全部的孩子。

運行該腳本應該中止命令提示符,直到全部五個孩子終止,這是理想的。

相關文章
相關標籤/搜索