php多进程应用场景实例详解


Posted in PHP onJuly 22, 2019

本文实例讲述了php多进程应用场景。分享给大家供大家参考,具体如下:

pcntl介绍

扩展介绍

php多进程模块依赖pcntl扩展,官方手册介绍:http://php.net/manual/zh/book.pcntl.php

Note:

1. 此扩展在 Windows 平台上不可用。
2. 进程控制不能被应用在Web服务器环境,当其被用于Web服务环境时可能会带来意外的结果。因此,不能再PHP Web开发中使用多进程。

安装扩展

# 通过pecl安装pcntl扩展
sudo pecl install pcntl
# 增加 extension=pcntl.so
sodo vim /etc/php.ini
# 检查扩展是否安装成功
php -m | grep pcntl

处理文件

当一个文件包含许多任务(每个任务一行),并且各任务之间不存在执行的先后顺序关系,可以将文件进行分割(分割后的文件数量与进程数一致),然后使用多进程进行处理。

例如,现在有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 -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

相关脚本

多进程调用脚本 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);
  }
}

多进程执行脚本 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;
}

输出结果

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中创建了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 = 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);
  }
}

多进程执行脚本 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;

输出结果

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

通过输出结果可以得到

1. a、b、c三个进程同时开始执行,时间戳为1504499844
2. a和c同时结束执行,分别处理了3条记录,时间戳为1504499850,耗时6s
3. b最后结束执行,处理了4条记录,时间戳为1504499852,耗时8s

master-worker模式

我们模拟Web服务器处理http请求的操作,对于每个请求创建一个进程,用于处理请求内容。

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);

输出结果:

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

如果依次处理请求,总耗时为1+2+3+4+5+6=21s。每个请求创建一个进程的处理方式,总耗时是最耗时的请求操作6s。

多进程最好在方法、函数或者文件中单独使用,这样逻辑更加清晰,也便于分析和维护。

附录

邮件操作类:

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);
    }
  }
}

希望本文所述对大家PHP程序设计有所帮助。

PHP 相关文章推荐
针对初学PHP者的疑难问答(1)
Oct 09 PHP
PHP 将图片按创建时间进行分类存储的实现代码
Jan 05 PHP
php的数组与字符串的转换函数整理汇总
Jul 18 PHP
PHP中使用FFMPEG获取视频缩略图和视频总时长实例
May 04 PHP
php中http与https跨域共享session的解决方法
Dec 20 PHP
php使用GD创建保持宽高比缩略图的方法
Apr 17 PHP
php实现购物车功能(上)
Jul 23 PHP
CI分页类首页、尾页不显示的解决方法
Mar 28 PHP
关于PHP文件的自动运行方法分析
May 13 PHP
session 加入redis的实现代码
Jul 15 PHP
php设计模式之策略模式实例分析【星际争霸游戏案例】
Mar 26 PHP
Laravel相关的一些故障解决
Aug 19 PHP
PHP实现的多进程控制demo示例
Jul 22 #PHP
php+lottery.js实现九宫格抽奖功能
Jul 21 #PHP
在 Laravel 项目中使用 webpack-encore的方法
Jul 21 #PHP
Smarty缓存机制实例详解【三种缓存方式】
Jul 20 #PHP
PHP INT类型在内存中占字节详解
Jul 20 #PHP
PHP检测一个数组有没有定义的方法步骤
Jul 20 #PHP
PHP defined()函数的使用图文详解
Jul 20 #PHP
You might like
【COS正片】蕾姆睡衣cos,纯洁可爱被治愈了 cn名濑弥七
2020/03/02 日漫
PHP - Html Transfer Code
2006/10/09 PHP
php录入页面中动态从数据库中提取数据的实现
2006/10/09 PHP
PHP文件大小格式化函数合集
2014/03/10 PHP
yii实现创建验证码实例解析
2014/07/31 PHP
ThinkPHP3.2.2实现持久登录(记住我)功能的方法
2016/05/16 PHP
php实现URL加密解密的方法
2016/11/17 PHP
php使用PDO从数据库表中读取数据的实现方法(必看)
2017/06/02 PHP
Laravel timestamps 设置为unix时间戳的方法
2019/10/11 PHP
javascript验证上传文件的类型限制必须为某些格式
2013/11/14 Javascript
javascript生成随机数的方法
2014/05/16 Javascript
jQuery中children()方法用法实例
2015/01/07 Javascript
js实现带按钮的上下滚动效果
2015/05/12 Javascript
jquery中cookie用法实例详解(获取,存储,删除等)
2016/01/04 Javascript
基于JavaScript实现div层跟随滚动条滑动
2016/01/12 Javascript
jquery easyUI中ajax异步校验用户名
2016/08/19 Javascript
js点击时关闭该范围下拉菜单之外的菜单方法
2018/01/11 Javascript
vue todo-list组件发布到npm上的方法
2018/04/04 Javascript
React中使用UEditor百度富文本的方法
2018/08/22 Javascript
node使用Mongoose类库实现简单的增删改查
2018/11/08 Javascript
利用Electron简单撸一个Markdown编辑器的方法
2019/06/10 Javascript
python用plt画图时,cmp设置方法
2018/12/13 Python
更新pip3与pyttsx3文字语音转换的实现方法
2019/08/08 Python
Python3 JSON编码解码方法详解
2019/09/06 Python
PyTorch和Keras计算模型参数的例子
2020/01/02 Python
利用PyQt中的QThread类实现多线程
2020/02/18 Python
Python 开发工具通过 agent 代理使用的方法
2020/09/27 Python
GIVENCHY纪梵希官方旗舰店:高定彩妆与贵族护肤品
2018/04/16 全球购物
什么时候用assert
2015/05/08 面试题
品牌转让协议书
2014/08/20 职场文书
个人股份合作协议书
2014/10/24 职场文书
工作简历的自我评价
2019/05/16 职场文书
2019已经过半,你知道年中工作总结该怎么写吗?
2019/07/03 职场文书
Requests什么的通通爬不了的Python超强反爬虫方案!
2021/05/20 Python
【海涛dota】偶遇拉娜娅 质量局德鲁伊第一视角解说
2022/04/01 DOTA
Nginx如何获取自定义请求header头和URL参数详解
2022/07/23 Servers