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 相关文章推荐
file_get_contents获取不到网页内容的解决方法
Mar 07 PHP
PHP英文字母大小写转换函数小结
May 03 PHP
CI使用Tank Auth转移数据库导致密码用户错误的解决办法
Jun 12 PHP
ThinkPHP3.1的Widget新用法
Jun 19 PHP
php简单实现短网址(短链)还原的方法(测试可用)
May 09 PHP
thinkphp3.x自定义Action、Model及View的简单实现方法
May 19 PHP
浅谈php fopen下载远程文件的函数
Nov 18 PHP
PHP字符串逆序排列实现方法小结【strrev函数,二分法,循环法,递归法】
Jan 13 PHP
浅谈PHP发送HTTP请求的几种方式
Jul 25 PHP
PHP设计模式之抽象工厂模式实例分析
Mar 25 PHP
laravel 事件/监听器实例代码
Apr 12 PHP
Laravel自定义 封装便捷返回Json数据格式的引用方法
Sep 29 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
DC漫画《蝙蝠侠和猫女》图透 猫女怀孕老爷当爹
2020/04/09 欧美动漫
用Apache反向代理设置对外的WWW和文件服务器
2006/10/09 PHP
转换中文日期的PHP程序
2006/10/09 PHP
解决了Ajax、MySQL 和 Zend Framework 的乱码问题
2009/03/03 PHP
用mysql_fetch_array()获取当前行数据的方法详解
2013/06/05 PHP
php计算当前程序执行时间示例
2014/04/24 PHP
filemanage功能中用到的common.js
2007/04/08 Javascript
jquery插件 cluetip 关键词注释
2010/01/12 Javascript
20款非常优秀的 jQuery 工具提示插件 推荐
2012/07/15 Javascript
JavaScript事件处理器中的event参数使用介绍
2013/05/24 Javascript
JavaScript实现从数组中选出和等于固定值的n个数
2014/09/03 Javascript
js 动态给元素添加、移除事件的实现方法
2016/07/19 Javascript
简单的js计算器实现
2016/10/26 Javascript
JS实现搜索关键词的智能提示功能
2017/07/07 Javascript
Vue精简版风格概述
2018/01/30 Javascript
原生JS实现$.param() 函数的方法
2018/08/10 Javascript
vue学习之Vue-Router用法实例分析
2020/01/06 Javascript
JavaScript 中的六种循环方法
2021/01/06 Javascript
[01:19:34]2014 DOTA2国际邀请赛中国区预选赛 New Element VS Dream time
2014/05/22 DOTA
Python中逗号的三种作用实例分析
2015/06/08 Python
Python 中的with关键字使用详解
2016/09/11 Python
Python实现的中国剩余定理算法示例
2017/08/05 Python
Python随机生成均匀分布在三角形内或者任意多边形内的点
2017/12/14 Python
使用pyqt5 tablewidget 单元格设置正则表达式
2019/12/13 Python
python实现word文档批量转成自定义格式的excel文档的思路及实例代码
2020/02/21 Python
解决import tensorflow as tf 出错的原因
2020/04/16 Python
python计算auc的方法
2020/09/09 Python
全球最大的服务市场:Fiverr
2017/01/03 全球购物
实习护理工作自我评价
2013/09/25 职场文书
留守儿童工作方案
2014/06/02 职场文书
学习张林森心得体会
2014/09/10 职场文书
工作经常出错的检讨书
2014/09/13 职场文书
局领导领导班子四风对照检查材料
2014/09/27 职场文书
SQL实现LeetCode(178.分数排行)
2021/08/04 MySQL
浅谈Python中对象是如何被调用的
2022/04/06 Python