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程序设计有所帮助。