php多進(jìn)程應(yīng)用場(chǎng)景實(shí)例詳解
本文實(shí)例講述了php多進(jìn)程應(yīng)用場(chǎng)景。分享給大家供大家參考,具體如下:
pcntl介紹
擴(kuò)展介紹
php多進(jìn)程模塊依賴pcntl擴(kuò)展,官方手冊(cè)介紹:http://php.net/manual/zh/book.pcntl.php
Note:
1. 此擴(kuò)展在 Windows 平臺(tái)上不可用。
2. 進(jìn)程控制不能被應(yīng)用在Web服務(wù)器環(huán)境,當(dāng)其被用于Web服務(wù)環(huán)境時(shí)可能會(huì)帶來(lái)意外的結(jié)果。因此,不能再PHP Web開(kāi)發(fā)中使用多進(jìn)程。
安裝擴(kuò)展
# 通過(guò)pecl安裝pcntl擴(kuò)展 sudo pecl install pcntl # 增加 extension=pcntl.so sodo vim /etc/php.ini # 檢查擴(kuò)展是否安裝成功 php -m | grep pcntl
處理文件
當(dāng)一個(gè)文件包含許多任務(wù)(每個(gè)任務(wù)一行),并且各任務(wù)之間不存在執(zhí)行的先后順序關(guān)系,可以將文件進(jìn)行分割(分割后的文件數(shù)量與進(jìn)程數(shù)一致),然后使用多進(jìn)程進(jìn)行處理。
例如,現(xiàn)在有10個(gè)郵箱賬號(hào)存儲(chǔ)在文件mailist.txt中,每次發(fā)送郵件需要耗時(shí)2s,則采用單進(jìn)程依次發(fā)送完這些郵件需要耗時(shí)20。
如果采用多進(jìn)程,例如3個(gè)進(jìn)程進(jìn)行處理,首先需要將文件按行數(shù)拆分成3個(gè)小文件,其中兩個(gè)文件是4條記錄,一個(gè)文件是2條記錄。每個(gè)進(jìn)程處理一個(gè)小文件,則不同進(jìn)程發(fā)送完郵件的耗時(shí)為8、8、6,總耗時(shí)取最大值為8s。
拆分文件
原始文件 maillist.txt
000000@163.com
111111@163.com
222222@163.com
333333@163.com
444444@163.com
555555@163.com
666666@163.com
777777@163.com
888888@163.com
999999@163.com
拆分操作
split -a 1 -l 4 maillist.txt task
拆分后的文件
taska
000000@163.com
111111@163.com
222222@163.com
333333@163.com
taskb
444444@163.com
555555@163.com
666666@163.com
777777@163.com
taskc
888888@163.com
999999@163.com
相關(guān)腳本
多進(jìn)程調(diào)用腳本 text_task.php
$cmds = [
['/Users/zhezhao/www/work/text_mail.php','a'],
['/Users/zhezhao/www/work/text_mail.php','b'],
['/Users/zhezhao/www/work/text_mail.php','c']
];
foreach ($cmds as $cmd){
$pid = pcntl_fork();
if($pid == -1){
exit('create process failed');
}
if($pid > 0){
pcntl_wait($status,WNOHANG);
}else{
pcntl_exec('/usr/bin/php',$cmd);
}
}
多進(jìn)程執(zhí)行腳本 text_mail.php
require 'MailWork.php';
$name = $argv[1];
echo $name." start #".time().PHP_EOL;
$worker = new MailWork($name);
$res = $worker->text_mail($argc,$argv);
if($res === false){
echo $worker->getLastError();
}else{
echo $name." ".$res." works done # ".time().PHP_EOL;
}
輸出結(jié)果
c start #1504499765
b start #1504499765
a start #1504499765
b#mailto:444444@163.com
c#mailto:888888@163.com
a#mailto:000000@163.com
b#mailto:555555@163.com
a#mailto:111111@163.com
c#mailto:999999@163.com
c 2 works done # 1504499769
a#mailto:222222@163.com
b#mailto:666666@163.com
b#mailto:777777@163.com
a#mailto:333333@163.com
b 4 works done # 1504499773
a 4 works done # 1504499773
在text_task.php中創(chuàng)建了3個(gè)進(jìn)程(a、b、c),其中a和b處理的文件中有4條記錄,c處理的文件中有2條記錄。
通過(guò)輸出結(jié)果可以得到:
1. a、b、c 三個(gè)進(jìn)程同時(shí)開(kāi)始執(zhí)行,開(kāi)始時(shí)間戳1504499765
2. c最先完成,完成時(shí)間戳1504499769,耗時(shí)4s
3. a和c同時(shí)完成,完成時(shí)間戳1504499773,耗時(shí)8s
處理消息隊(duì)列
這是另外一種常見(jiàn)的常見(jiàn),我們將耗時(shí)操作放入消息隊(duì)列,通過(guò)腳本從消息隊(duì)列中取出并執(zhí)行記錄。如果通過(guò)單個(gè)進(jìn)程依次讀取并處理消息,容易使隊(duì)列中積累大量數(shù)據(jù),導(dǎo)致操作的延遲時(shí)間較長(zhǎng),這種場(chǎng)景可以通過(guò)多個(gè)進(jìn)程來(lái)讀取并處理消息。redis中的pop操作具有原子性,不會(huì)存在多個(gè)讀取到相同的隊(duì)列消息的情況。
多進(jìn)程調(diào)用腳本 redis_task.php
$redis = new Redis();
$redis->connect('192.168.1.10');
$task_key = 'task_list';
$task_list = [
'000000@163.com',
'111111@163.com',
'222222@163.com',
'333333@163.com',
'444444@163.com',
'555555@163.com',
'666666@163.com',
'777777@163.com',
'888888@163.com',
'999999@163.com',
];
foreach ($task_list as $task){
$redis->lPush($task_key,$task);
}
$cmds = [
['/Users/zhezhao/www/work/redis_mail.php','a'],
['/Users/zhezhao/www/work/redis_mail.php','b'],
['/Users/zhezhao/www/work/redis_mail.php','c']
];
foreach ($cmds as $cmd){
$pid = pcntl_fork();
if($pid == -1){
exit('create process failed');
}
if($pid > 0){
pcntl_wait($status,WNOHANG);
}else{
pcntl_exec('/usr/bin/php',$cmd);
}
}
多進(jìn)程執(zhí)行腳本 redis_mail.php
require 'MailWork.php';
$name = $argv[1];
echo $name." start #".time().PHP_EOL;
$worker = new MailWork($name);
$redis = new Redis();
$redis->connect('192.168.1.10');
$task_key = 'task_list';
while($redis->lLen($task_key)>0){
$mailto = $redis->rPop($task_key);
$worker->redis_mail($mailto);
}
echo $name." work done # ".time().PHP_EOL;
輸出結(jié)果
b start #1504499844
c start #1504499844
a start #1504499844
b#mailto:000000@163.com
a#mailto:111111@163.com
c#mailto:222222@163.com
b#mailto:333333@163.com
a#mailto:444444@163.com
c#mailto:555555@163.com
b#mailto:666666@163.com
a#mailto:777777@163.com
c#mailto:888888@163.com
c work done # 1504499850
a work done # 1504499850
b#mailto:999999@163.com
b work done # 1504499852
通過(guò)輸出結(jié)果可以得到
1. a、b、c三個(gè)進(jìn)程同時(shí)開(kāi)始執(zhí)行,時(shí)間戳為1504499844
2. a和c同時(shí)結(jié)束執(zhí)行,分別處理了3條記錄,時(shí)間戳為1504499850,耗時(shí)6s
3. b最后結(jié)束執(zhí)行,處理了4條記錄,時(shí)間戳為1504499852,耗時(shí)8s
master-worker模式
我們模擬Web服務(wù)器處理http請(qǐng)求的操作,對(duì)于每個(gè)請(qǐng)求創(chuàng)建一個(gè)進(jìn)程,用于處理請(qǐng)求內(nèi)容。
class WebServer
{
private $list;
public function __construct()
{
$this->list = [];
}
public function worker($request){
$pid = pcntl_fork();
if($pid == -1){
return false;
}
if($pid > 0){
return $pid;
}
if($pid == 0){
$time = $request[0];
$method = $request[1];
$start = time();
echo getmypid()."\t start ".$method."\tat".$start.PHP_EOL;
sleep($time);
$end = time();
$cost = $end-$start;
echo getmypid()."\t stop \t".$method."\tat:".$end."\tcost:".$cost.PHP_EOL;
exit(0);
}
}
public function master($requests){
$start = time();
echo "All request handle stop at ".$start.PHP_EOL;
foreach ($requests as $request){
$pid = $this->worker($request);
if(!$pid){
echo 'handle fail!'.PHP_EOL;
return;
}
array_push($this->list,$pid);
}
while(count($this->list)>0){
foreach ($this->list as $k=>$pid){
$res = pcntl_waitpid($pid,$status,WNOHANG);
if($res == -1 || $res > 0){
unset($this->list[$k]);
}
}
usleep(100);
}
$end = time();
$cost = $end - $start;
echo "All request handle stop at ".$end."\t cost:".$cost.PHP_EOL;
}
}
$requests = [
[1,'GET index.php'],
[2,'GET index.php'],
[3,'GET index.php'],
[4,'GET index.php'],
[5,'GET index.php'],
[6,'GET index.php']
];
$server = new WebServer();
$server->master($requests);
輸出結(jié)果:
All request handle stop at 1504513048
18945 start GET index.php at1504513048
18944 start GET index.php at1504513048
18946 start GET index.php at1504513048
18947 start GET index.php at1504513048
18948 start GET index.php at1504513048
18949 start GET index.php at1504513048
18944 stop GET index.php at:1504513049 cost:1
18945 stop GET index.php at:1504513050 cost:2
18946 stop GET index.php at:1504513051 cost:3
18947 stop GET index.php at:1504513052 cost:4
18948 stop GET index.php at:1504513053 cost:5
18949 stop GET index.php at:1504513054 cost:6
All request handle stop at 1504513054 cost:6
如果依次處理請(qǐng)求,總耗時(shí)為1+2+3+4+5+6=21s。每個(gè)請(qǐng)求創(chuàng)建一個(gè)進(jìn)程的處理方式,總耗時(shí)是最耗時(shí)的請(qǐng)求操作6s。
多進(jìn)程最好在方法、函數(shù)或者文件中單獨(dú)使用,這樣邏輯更加清晰,也便于分析和維護(hù)。
附錄
郵件操作類:
MailWork.php
<?php
/**
* Created by PhpStorm.
* User: zhezhao
* Date: 2017/9/4
* Time: 上午10:05
*/
class MailWork
{
private $error;
private $name;
public function __construct($name)
{
$this->name = $name;
}
public function getLastError(){
return $this->error;
}
public function checkEnv($argc)
{
if (substr(php_sapi_name(), 0, 3) !== 'cli') {
$this->error ="This Programe can only be run in CLI mode";
return false;
}
if($argc!=2){
$this->error = 'wrong params';
return false;
}
return true;
}
public function getFileName($argv){
$filename = "task".$argv[1];
if(!file_exists($filename)){
$this->error = 'file does not exits';
return false;
}else{
return $filename;
}
}
public function sendMail($mailto)
{
sleep(2);
echo $this->name."#mailto:".$mailto.PHP_EOL;
}
public function text_mail($argc,$argv){
if(!$this->checkEnv($argc)){
return false;
}
$filename = $this->getFileName($argv);
if(!$filename){
return false;
}
$fp = fopen($filename,'r');
$counter = 0;
while(!feof($fp)){
$line = fgets($fp);
$mailto = substr($line,0,strlen($line)-1);
if(preg_match('/^\w+@\w+\.\w+$/',$mailto)){
$this->sendMail($mailto);
$counter++;
}
}
return $counter;
}
public function redis_mail($mailto){
if(preg_match('/^\w+@\w+\.\w+$/',$mailto)){
$this->sendMail($mailto);
}
}
}
更多關(guān)于PHP相關(guān)內(nèi)容感興趣的讀者可查看本站專題:《PHP進(jìn)程與線程操作技巧總結(jié)》、《PHP網(wǎng)絡(luò)編程技巧總結(jié)》、《PHP基本語(yǔ)法入門(mén)教程》、《PHP數(shù)組(Array)操作技巧大全》、《php字符串(string)用法總結(jié)》、《php+mysql數(shù)據(jù)庫(kù)操作入門(mén)教程》及《php常見(jiàn)數(shù)據(jù)庫(kù)操作技巧匯總》
希望本文所述對(duì)大家PHP程序設(shè)計(jì)有所幫助。
- PHP實(shí)現(xiàn)的多進(jìn)程控制demo示例
- 深入探究PHP的多進(jìn)程編程方法
- PHP多進(jìn)程之pcntl_fork的實(shí)例詳解
- PHP使用pcntl_fork實(shí)現(xiàn)多進(jìn)程下載圖片的方法
- PHP的pcntl多進(jìn)程用法實(shí)例
- php中實(shí)現(xiàn)進(jìn)程鎖與多進(jìn)程的方法
- Linux下實(shí)現(xiàn)PHP多進(jìn)程的方法分享
- PHP多進(jìn)程編程總結(jié)(推薦)
- 分享PHP-pcntl 實(shí)現(xiàn)多進(jìn)程代碼
- php多進(jìn)程模擬并發(fā)事務(wù)產(chǎn)生的問(wèn)題小結(jié)
- PHP多進(jìn)程通信-消息隊(duì)列使用
相關(guān)文章
php+javascript實(shí)現(xiàn)的動(dòng)態(tài)顯示服務(wù)器運(yùn)行程序進(jìn)度條功能示例
這篇文章主要介紹了php+javascript實(shí)現(xiàn)的動(dòng)態(tài)顯示服務(wù)器運(yùn)行程序進(jìn)度條功能,涉及php結(jié)合javascript數(shù)學(xué)運(yùn)算與緩沖輸出相關(guān)操作技巧,需要的朋友可以參考下2017-08-08
PHP中獲取內(nèi)網(wǎng)用戶MAC地址(WINDOWS/linux)的實(shí)現(xiàn)代碼
做一個(gè)內(nèi)網(wǎng)根據(jù)MAC地址自動(dòng)登錄的應(yīng)用,在WINDOW 2003可以正常使用,函數(shù)如下2011-08-08
基于PHP實(shí)現(xiàn)的事件機(jī)制實(shí)例分析
這篇文章主要介紹了基于PHP實(shí)現(xiàn)的事件機(jī)制,實(shí)例分析了事件機(jī)制的原理及php中debug_backtrace函數(shù)完成事件機(jī)制的實(shí)現(xiàn)技巧,需要的朋友可以參考下2015-06-06
PHP實(shí)現(xiàn)適用于自定義的驗(yàn)證碼類
這篇文章主要為大家詳細(xì)介紹了PHP實(shí)現(xiàn)適用于自定義的驗(yàn)證碼類,使用對(duì)象編寫(xiě)的驗(yàn)證碼類,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-06-06
php中突破基于HTTP_REFERER的防盜鏈措施(stream_context_create)
如果考慮突破防盜鏈的措施,就需要考慮在 HTTP_REFERER 上面做手腳了。很多網(wǎng)站是通過(guò)referer來(lái)判斷是否盜鏈。2011-03-03
PHP去除數(shù)組中重復(fù)的元素并按鍵名排序函數(shù)
用php實(shí)現(xiàn)的去除數(shù)組中重復(fù)的函數(shù)2008-08-08

