swoole_process实现进程池的方法示例


Posted in PHP onOctober 29, 2018

swoole —— 重新定义PHP

swoole 的进程之间有两种通信方式,一种是消息队列(queue),另一种是管道(pipe),对swoole_process 的研究在swoole中显得尤为重要。

预备知识

IO多路复用

swoole 中的io多路复用表现为底层的 epoll进程模型,在C语言中表现为 epoll 函数。

  • epoll 模型下会持续监听自己名下的素有socket 描述符 fd
  • 当触发了 socket 监听的事件时,epoll 函数才会响应,并返回所有监听该时间的 socket 集合
  • epoll 的本质是阻塞IO,它的优点在于能同事处理大量socket连接

Event loop 事件循环

swoole 对 epoll 实现了一个Reactor线程模型封装,设置了read事件和write事件的监听回调函数。(详见swoole_event_add)

  • Event loop 是一个Reactor线程,其中运行了一个epoll实例。
  • 通过swoole_event_add将socket描述符的一个事件添加到epoll监听中,事件发生时将执行回调函数
  • 不可用于fpm环境下,因为fpm在任务结束时可能会关掉进程。

swoole_process

  • 基于C语言封装的进程管理模块,方便php来调用
  • 内置管道、消息队列接口,方便实现进程间通信

我们在php-fpm.conf配置文件中发现,php-fpm中有两种进程池管理设置。

  • 静态模式 即初始化固定的进程数,当来了一个请求时,从中选取一个进程来处理。
  • 动态模式 指定最小、最大进程数,当请求量过大,进程数不超过最大限制时,新增线程去处理请求

接下来用swoole代码来实现,这里只是为理解swoole_process、进程间通信、定时器等使用,实际情况使用封装好的swoole_server来实现task任务队列池会更方便。

假如有个定时投递的任务队列:

<?php

/**
 * 动态进程池,类似fpm
 * 动态新建进程
 * 有初始进程数,最小进程数,进程不够处理时候新建进程,不超过最大进程数
 */

// 一个进程定时投递任务

/**
 * 1. tick
 * 2. process及其管道通讯
 * 3. event loop 事件循环
 */
class processPool
{
  private $pool;

  /**
   * @var swoole_process[] 记录所有worker的process对象
   */
  private $workers = [];

  /**
   * @var array 记录worker工作状态
   */
  private $used_workers = [];

  /**
   * @var int 最小进程数
   */
  private $min_woker_num = 5;

  /**
   * @var int 初始进程数
   */
  private $start_worker_num = 10;

  /**
   * @var int 最大进程数
   */
  private $max_woker_num = 20;

  /**
   * 进程闲置销毁秒数
   * @var int
   */
  private $idle_seconds = 5;

  /**
   * @var int 当前进程数
   */
  private $curr_num;

  /**
   * 闲置进程时间戳
   * @var array
   */
  private $active_time = [];

  public function __construct()
  {
    $this->pool = new swoole_process(function () {
      // 循环建立worker进程
      for ($i = 0; $i < $this->start_worker_num; $i++) {
        $this->createWorker();
      }
      echo '初始化进程数:' . $this->curr_num . PHP_EOL;
      // 每秒定时往闲置的worker的管道中投递任务
      swoole_timer_tick(1000, function ($timer_id) {
        static $count = 0;
        $count++;
        $need_create = true;
        foreach ($this->used_workers as $pid => $used) {
          if ($used == 0) {
            $need_create = false;
            $this->workers[$pid]->write($count . ' job');
            // 标记使用中
            $this->used_workers[$pid] = 1;
            $this->active_time[$pid] = time();
            break;
          }
        }
        foreach ($this->used_workers as $pid => $used)
          // 如果所有worker队列都没有闲置的,则新建一个worker来处理
          if ($need_create && $this->curr_num < $this->max_woker_num) {
            $new_pid = $this->createWorker();
            $this->workers[$new_pid]->write($count . ' job');
            $this->used_workers[$new_pid] = 1;
            $this->active_time[$new_pid] = time();
          }

        // 闲置超过一段时间则销毁进程
        foreach ($this->active_time as $pid => $timestamp) {
          if ((time() - $timestamp) > $this->idle_seconds && $this->curr_num > $this->min_woker_num) {
            // 销毁该进程
            if (isset($this->workers[$pid]) && $this->workers[$pid] instanceof swoole_process) {
              $this->workers[$pid]->write('exit');
              unset($this->workers[$pid]);
              $this->curr_num = count($this->workers);
              unset($this->used_workers[$pid]);
              unset($this->active_time[$pid]);
              echo "{$pid} destroyed\n";
              break;
            }
          }
        }

        echo "任务{$count}/{$this->curr_num}\n";

        if ($count == 20) {
          foreach ($this->workers as $pid => $worker) {
            $worker->write('exit');
          }
          // 关闭定时器
          swoole_timer_clear($timer_id);
          // 退出进程池
          $this->pool->exit(0);
          exit();
        }
      });

    });

    $master_pid = $this->pool->start();
    echo "Master $master_pid start\n";

    while ($ret = swoole_process::wait()) {
      $pid = $ret['pid'];
      echo "process {$pid} existed\n";
    }
  }

  /**
   * 创建一个新进程
   * @return int 新进程的pid
   */
  public function createWorker()
  {
    $worker_process = new swoole_process(function (swoole_process $worker) {
      // 给子进程管道绑定事件
      swoole_event_add($worker->pipe, function ($pipe) use ($worker) {
        $data = trim($worker->read());
        if ($data == 'exit') {
          $worker->exit(0);
          exit();
        }
        echo "{$worker->pid} 正在处理 {$data}\n";
        sleep(5);
        // 返回结果,表示空闲
        $worker->write("complete");
      });
    });

    $worker_pid = $worker_process->start();

    // 给父进程管道绑定事件
    swoole_event_add($worker_process->pipe, function ($pipe) use ($worker_process) {
      $data = trim($worker_process->read());
      if ($data == 'complete') {
        // 标记为空闲
//        echo "{$worker_process->pid} 空闲了\n";
        $this->used_workers[$worker_process->pid] = 0;
      }
    });

    // 保存process对象
    $this->workers[$worker_pid] = $worker_process;
    // 标记为空闲
    $this->used_workers[$worker_pid] = 0;
    $this->active_time[$worker_pid] = time();
    $this->curr_num = count($this->workers);
    return $worker_pid;
  }

}

new processPool();

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

PHP 相关文章推荐
社区(php&amp;&amp;mysql)一
Oct 09 PHP
浅析php中抽象类和接口的概念以及区别
Jun 27 PHP
php5.3 注意事项说明
Jul 01 PHP
深入解读php中关于抽象(abstract)类和抽象方法的问题分析
Jan 03 PHP
使用ob系列函数实现PHP网站页面静态化
Aug 13 PHP
php中array_multisort对多维数组排序的方法
Jun 21 PHP
详解WordPress中给链接添加查询字符串的方法
Dec 18 PHP
thinkPHP中多维数组的遍历方法
Jan 09 PHP
php结合md5实现的加密解密方法
Jan 25 PHP
Yii2使用swiftmailer发送邮件的方法
May 03 PHP
利用php做服务器和web前端的界面进行交互
Oct 31 PHP
PHP实现权限管理功能示例
Sep 22 PHP
PHP大文件分片上传的实现方法
Oct 28 #PHP
PHP array_reduce()函数的应用解析
Oct 28 #PHP
php 中phar包的使用教程详解
Oct 26 #PHP
Linux基于php-fpm模式的lamp搭建phpmyadmin的方法
Oct 25 #PHP
phpstudy2018升级MySQL5.5为5.7教程(图文)
Oct 24 #PHP
实例解析php的数据类型
Oct 24 #PHP
网站被恶意镜像怎么办 php一段代码轻松搞定(全面版)
Oct 23 #PHP
You might like
php不用内置函数对数组排序的两个算法代码
2010/02/08 PHP
Codeigniter+PHPExcel实现导出数据到Excel文件
2014/06/12 PHP
php+jQuery实现的三级导航栏下拉菜单显示效果
2017/08/10 PHP
详解php用static方法的原因
2018/09/12 PHP
PHP 获取客户端 IP 地址的方法实例代码
2018/11/11 PHP
jQuery 获取URL参数的插件
2010/03/04 Javascript
JavaScript中的面向对象介绍
2012/06/30 Javascript
JS验证邮件地址格式方法小结
2015/12/01 Javascript
JS框架之vue.js(深入三:组件1)
2016/09/29 Javascript
AngularJs返回前一页面时刷新一次前面页面的方法
2018/10/09 Javascript
vue axios请求频繁时取消上一次请求的方法
2018/11/10 Javascript
解决微信小程序调用moveToLocation失效问题【超简单】
2019/04/12 Javascript
Vue 使用formData方式向后台发送数据的实现
2019/04/14 Javascript
Layui之table中的radio在切换分页时无法记住选中状态的解决方法
2019/09/02 Javascript
Javascript组合继承方法代码实例解析
2020/04/02 Javascript
基于JavaScript实现大文件上传后端代码实例
2020/08/18 Javascript
[19:59]2014DOTA2国际邀请赛 IG战队纪录片
2014/08/07 DOTA
学习python的几条建议分享
2013/02/10 Python
Python中lambda的用法及其与def的区别解析
2014/07/28 Python
详解Python设计模式编程中观察者模式与策略模式的运用
2016/03/02 Python
Odoo中如何生成唯一不重复的序列号详解
2018/02/10 Python
Python入门学习指南分享
2018/04/11 Python
对TensorFlow中的variables_to_restore函数详解
2018/07/30 Python
Python Flask 搭建微信小程序后台详解
2019/05/06 Python
使用Keras 实现查看model weights .h5 文件的内容
2020/06/09 Python
pandas抽取行列数据的几种方法
2020/12/13 Python
利用CSS3的线性渐变linear-gradient制作边框的示例
2016/06/02 HTML / CSS
佳能英国官方网站:Canon UK
2017/08/08 全球购物
巴西一家专门从事家居和装饰的连锁店:Camicado
2019/08/14 全球购物
初中三年毕业生的自我评价分享
2014/02/14 职场文书
英文求职信写作小建议
2014/02/16 职场文书
顶碗少年教学反思
2014/02/21 职场文书
三八妇女节慰问信
2015/02/14 职场文书
使用vue-element-admin框架从后端动态获取菜单功能的实现
2021/04/29 Vue.js
Django使用channels + websocket打造在线聊天室
2021/05/20 Python
vue-cli3.0修改打包后的文件名和文件地址,打包后本地运行报错解决
2022/04/06 Vue.js