php多進程編程詳解2017

來源:文萃谷 2.72W

日常任務中,有時需要通過php腳本執行一些日誌分析,隊列處理等任務,當數據量比較大時,可以使用多進程來處理。php多進程是怎麼寫的?下面yjbys小編為大家分享php多進程編程,希望對大家有參考作用!

php多進程編程詳解2017

  php單進程存在的問題:

多核處理器未充分利用,而單處理器通常需要等待其他操作完成之後才能再繼續工作。 任何現代操作系統都可在幕後執行多任務,這意味着在很短時間內,計算機可以調度多個進程,以執行多個程序。

如果我們將所有的工作都侷限在一個進程中,它只能一次做一件事,這意味着我們需要將我們的單進程任務變成一個多進程任務,以便我們可以利用 操作系統的多任務處理能力

  多進程與多線程

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

進程,是具有其自己的存儲器空間,自己的進程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,則當前腳本是被分叉的子節點。

  pcntl_fork執行原理

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

現在,這裏有一個基本使用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 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==$pid3n";

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

current_process_id===13090===pid1==13091===pid2===13092==pid3==13093current_process_id===13093===pid1==13091===pid2===13092==pid3==0current_process_id===13092===pid1==13091===pid2===0==pid3==13094current_process_id===13094===pid1==13091===pid2===0==pid3==0current_process_id===13091===pid1==0===pid2===13095==pid3==13096current_process_id===13096===pid1==0===pid2===13095==pid3==0current_process_id===13095===pid1==0===pid2===0==pid3==13097current_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_idn"; 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==$pidn"; sleep(1); //sleep($i) print "In child $in"; //這裏設置sleep不會阻塞輸出,1s後會自動結束進程

//sleep(1); //結束當前子進程,不讓子進程繼續fork,不會阻止父進程繼續fork

exit;

} else{

echo "parent current process id==$current_process_id==pid==$pidn"; print "In parent $in"; //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 the main process id==13163parent current process id==13163==pid==13164In parent 1parent current process id==13163==pid==13165In parent 2parent current process id==13163==pid==13166In parent 3parent current process id==13163==pid==13167In parent 4parent current process id==13163==pid==13168In parent 5child 3 current process id==13166==pid==0child 2 current process id==13165==pid==0child 4 current process id==13167==pid==0child 5 current process id==13168==pid==0child 1 current process id==13164==pid==0[root@25f0b49dc696 wwwroot]# In child 3In child 4In child 5In child 2In 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_idn"; exit($i);

}

} while (($pid = pcntl_waitpid(0, $status)) != -1) {

$status = pcntl_wexitstatus($status);

echo "Child $status completed==pid==$pidn";

}

?>

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

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

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

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

熱門標籤