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环境搭建最新方法
Sep 05 PHP
php横向重复区域显示二法
Sep 25 PHP
使ecshop模板中可引用常量的实现方法
Jun 02 PHP
PHP APC的安装与使用详解
Jun 13 PHP
解析WordPress中函数钩子hook的作用及基本用法
Dec 22 PHP
Yii2验证器(Validator)用法分析
Jul 23 PHP
laravel 中如何使用ajax和vue总结
Aug 16 PHP
PHP基于curl实现模拟微信浏览器打开微信链接的方法示例
Feb 15 PHP
thinkPHP5.1框架路由::get、post请求简单用法示例
May 06 PHP
php/JS实现的生成随机密码(验证码)功能示例
Jun 06 PHP
Laravel第三方包报class not found的解决方法
Oct 13 PHP
php实现统计IP数及在线人数的示例代码
Jul 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
ThinkPHP实现动态包含文件的方法
2014/11/29 PHP
PHP中empty,isset,is_null用法和区别
2017/02/19 PHP
jQuery 1.0.4 - New Wave Javascript(js源文件)
2007/01/15 Javascript
浅析JavaScript中的CSS属性及命名规范
2013/11/28 Javascript
javascript删除数组元素并且数组长度减小的简单实例
2014/02/14 Javascript
JS倒计时代码汇总
2014/11/25 Javascript
Jquery 整理元素选取、常用方法一览表
2016/11/26 Javascript
Javascript中构造函数要注意的一些坑
2017/01/23 Javascript
Node.js+ES6+dropload.js实现移动端下拉加载实例
2017/06/01 Javascript
JS库之wow.js使用方法
2017/09/14 Javascript
通过一个简单的例子学会vuex与模块化
2017/11/22 Javascript
Bootstrap modal只加载一次数据的解决办法(推荐)
2017/11/24 Javascript
Vue 项目部署到服务器的问题解决方法
2017/12/05 Javascript
JS实现的JSON序列化操作简单示例
2018/07/02 Javascript
详解Vue CLI3 多页应用实践和源码设计
2018/08/30 Javascript
Vue列表渲染的示例代码
2018/11/01 Javascript
Vue中computed及watch区别实例解析
2020/08/01 Javascript
[02:56]DOTA2矮人直升机 英雄基础教程
2013/11/26 DOTA
[00:35]DOTA2上海特级锦标赛 MVP.Phx战队宣传片
2016/03/04 DOTA
[01:38]完美世界高校联赛决赛花絮
2018/12/02 DOTA
[01:20:30]OG vs LGD 2018国际邀请赛淘汰赛BO3 第四场 8.26
2018/08/30 DOTA
[01:06]DOTA2小知识课堂 Ep.02 吹风竟可解梦境缠绕
2019/12/05 DOTA
跟老齐学Python之有容乃大的list(1)
2014/09/14 Python
python实现树形打印目录结构
2018/03/29 Python
python 执行终端/控制台命令的例子
2019/07/12 Python
python实现按行分割文件
2019/07/22 Python
使用python实现男神女神颜值打分系统(推荐)
2019/10/31 Python
python中rb含义理解
2020/06/18 Python
Python利用命名空间解析XML文档
2020/08/10 Python
Square Off美国/加拿大:世界上最聪明的国际象棋棋盘
2018/12/06 全球购物
印尼第一大家居、生活和家具电子商务:Ruparupa
2019/11/25 全球购物
会计系个人求职信范文分享
2013/12/20 职场文书
出国导师推荐信
2015/03/25 职场文书
焦裕禄纪念馆观后感
2015/06/09 职场文书
2016新教师岗前培训心得体会
2016/01/08 职场文书
何时使用Map来代替普通的JS对象
2021/04/29 Javascript