php多进程应用场景实例详解
本文实例讲述了php多进程应用场景。分享给大家供大家参考,具体如下:
pcntl介绍
扩展介绍
php多进程模块依赖pcntl扩展,官方手册介绍:http://php.net/manual/zh/book.pcntl.php
Note:
1.此扩展在Windows平台上不可用。
2.进程控制不能被应用在Web服务器环境,当其被用于Web服务环境时可能会带来意外的结果。因此,不能再PHPWeb开发中使用多进程。
安装扩展
#通过pecl安装pcntl扩展 sudopeclinstallpcntl #增加extension=pcntl.so sodovim/etc/php.ini #检查扩展是否安装成功 php-m|greppcntl
处理文件
当一个文件包含许多任务(每个任务一行),并且各任务之间不存在执行的先后顺序关系,可以将文件进行分割(分割后的文件数量与进程数一致),然后使用多进程进行处理。
例如,现在有10个邮箱账号存储在文件mailist.txt中,每次发送邮件需要耗时2s,则采用单进程依次发送完这些邮件需要耗时20。
如果采用多进程,例如3个进程进行处理,首先需要将文件按行数拆分成3个小文件,其中两个文件是4条记录,一个文件是2条记录。每个进程处理一个小文件,则不同进程发送完邮件的耗时为8、8、6,总耗时取最大值为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-a1-l4maillist.txttask
拆分后的文件
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
相关脚本
多进程调用脚本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($cmdsas$cmd){
$pid=pcntl_fork();
if($pid==-1){
exit('createprocessfailed');
}
if($pid>0){
pcntl_wait($status,WNOHANG);
}else{
pcntl_exec('/usr/bin/php',$cmd);
}
}
多进程执行脚本text_mail.php
require'MailWork.php';
$name=$argv[1];
echo$name."start#".time().PHP_EOL;
$worker=newMailWork($name);
$res=$worker->text_mail($argc,$argv);
if($res===false){
echo$worker->getLastError();
}else{
echo$name."".$res."worksdone#".time().PHP_EOL;
}
输出结果
cstart#1504499765
bstart#1504499765
astart#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
c2worksdone#1504499769
a#mailto:222222@163.com
b#mailto:666666@163.com
b#mailto:777777@163.com
a#mailto:333333@163.com
b4worksdone#1504499773
a4worksdone#1504499773
在text_task.php中创建了3个进程(a、b、c),其中a和b处理的文件中有4条记录,c处理的文件中有2条记录。
通过输出结果可以得到:
1.a、b、c三个进程同时开始执行,开始时间戳1504499765
2.c最先完成,完成时间戳1504499769,耗时4s
3.a和c同时完成,完成时间戳1504499773,耗时8s
处理消息队列
这是另外一种常见的常见,我们将耗时操作放入消息队列,通过脚本从消息队列中取出并执行记录。如果通过单个进程依次读取并处理消息,容易使队列中积累大量数据,导致操作的延迟时间较长,这种场景可以通过多个进程来读取并处理消息。redis中的pop操作具有原子性,不会存在多个读取到相同的队列消息的情况。
多进程调用脚本redis_task.php
$redis=newRedis();
$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_listas$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($cmdsas$cmd){
$pid=pcntl_fork();
if($pid==-1){
exit('createprocessfailed');
}
if($pid>0){
pcntl_wait($status,WNOHANG);
}else{
pcntl_exec('/usr/bin/php',$cmd);
}
}
多进程执行脚本redis_mail.php
require'MailWork.php';
$name=$argv[1];
echo$name."start#".time().PHP_EOL;
$worker=newMailWork($name);
$redis=newRedis();
$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."workdone#".time().PHP_EOL;
输出结果
bstart#1504499844
cstart#1504499844
astart#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
cworkdone#1504499850
aworkdone#1504499850
b#mailto:999999@163.com
bworkdone#1504499852
通过输出结果可以得到
1.a、b、c三个进程同时开始执行,时间戳为1504499844
2.a和c同时结束执行,分别处理了3条记录,时间戳为1504499850,耗时6s
3.b最后结束执行,处理了4条记录,时间戳为1504499852,耗时8s
master-worker模式
我们模拟Web服务器处理http请求的操作,对于每个请求创建一个进程,用于处理请求内容。
classWebServer
{
private$list;
publicfunction__construct()
{
$this->list=[];
}
publicfunctionworker($request){
$pid=pcntl_fork();
if($pid==-1){
returnfalse;
}
if($pid>0){
return$pid;
}
if($pid==0){
$time=$request[0];
$method=$request[1];
$start=time();
echogetmypid()."\tstart".$method."\tat".$start.PHP_EOL;
sleep($time);
$end=time();
$cost=$end-$start;
echogetmypid()."\tstop\t".$method."\tat:".$end."\tcost:".$cost.PHP_EOL;
exit(0);
}
}
publicfunctionmaster($requests){
$start=time();
echo"Allrequesthandlestopat".$start.PHP_EOL;
foreach($requestsas$request){
$pid=$this->worker($request);
if(!$pid){
echo'handlefail!'.PHP_EOL;
return;
}
array_push($this->list,$pid);
}
while(count($this->list)>0){
foreach($this->listas$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"Allrequesthandlestopat".$end."\tcost:".$cost.PHP_EOL;
}
}
$requests=[
[1,'GETindex.php'],
[2,'GETindex.php'],
[3,'GETindex.php'],
[4,'GETindex.php'],
[5,'GETindex.php'],
[6,'GETindex.php']
];
$server=newWebServer();
$server->master($requests);
输出结果:
Allrequesthandlestopat1504513048
18945 startGETindex.php at1504513048
18944 startGETindex.php at1504513048
18946 startGETindex.php at1504513048
18947 startGETindex.php at1504513048
18948 startGETindex.php at1504513048
18949 startGETindex.php at1504513048
18944 stop GETindex.php at:1504513049 cost:1
18945 stop GETindex.php at:1504513050 cost:2
18946 stop GETindex.php at:1504513051 cost:3
18947 stop GETindex.php at:1504513052 cost:4
18948 stop GETindex.php at:1504513053 cost:5
18949 stop GETindex.php at:1504513054 cost:6
Allrequesthandlestopat1504513054 cost:6
如果依次处理请求,总耗时为1+2+3+4+5+6=21s。每个请求创建一个进程的处理方式,总耗时是最耗时的请求操作6s。
多进程最好在方法、函数或者文件中单独使用,这样逻辑更加清晰,也便于分析和维护。
附录
邮件操作类:
MailWork.php
name=$name;
}
publicfunctiongetLastError(){
return$this->error;
}
publicfunctioncheckEnv($argc)
{
if(substr(php_sapi_name(),0,3)!=='cli'){
$this->error="ThisProgramecanonlyberuninCLImode";
returnfalse;
}
if($argc!=2){
$this->error='wrongparams';
returnfalse;
}
returntrue;
}
publicfunctiongetFileName($argv){
$filename="task".$argv[1];
if(!file_exists($filename)){
$this->error='filedoesnotexits';
returnfalse;
}else{
return$filename;
}
}
publicfunctionsendMail($mailto)
{
sleep(2);
echo$this->name."#mailto:".$mailto.PHP_EOL;
}
publicfunctiontext_mail($argc,$argv){
if(!$this->checkEnv($argc)){
returnfalse;
}
$filename=$this->getFileName($argv);
if(!$filename){
returnfalse;
}
$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;
}
publicfunctionredis_mail($mailto){
if(preg_match('/^\w+@\w+\.\w+$/',$mailto)){
$this->sendMail($mailto);
}
}
}
更多关于PHP相关内容感兴趣的读者可查看本站专题:《PHP进程与线程操作技巧总结》、《PHP网络编程技巧总结》、《PHP基本语法入门教程》、《PHP数组(Array)操作技巧大全》、《php字符串(string)用法总结》、《php+mysql数据库操作入门教程》及《php常见数据库操作技巧汇总》
希望本文所述对大家PHP程序设计有所帮助。