Swoole_process实现进程池的方法
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();
Swoole_process实现进程池的方法的更多相关文章
- python摸爬滚打之day032 管道 数据共享 进程池
1.进程池 当有成千上万个任务需要被执行的时候,有了进程池我们就不必去创建大量的进程. 首先,创建进程需要消耗时间,销毁进程(空间,变量,文件信息等等的内容)也需要消耗时间, 第二即便开启了成千上万的 ...
- Python并发编程-进程池的返回值
同步或异步获取返回值 #p = Pool() #p.map(funcname,iterable) 默认异步的执行任务,且自带close,join功能 #p.apply(), 同步调用进程池的方法 #p ...
- 线程queue、线程进程池、异步回调机制
1. 线程 queue queue is especially useful in threaded programming when information must be exchanged sa ...
- Python-进程-进程池-原理
进程 资源集合,调度和分配资源,说到进程就不得不提到线程,线程和进程是密不可分,进程申请了资源,但真正使用资源的是线程,其实本质上类似面向对象的思想,面向对象把数据和数据的操作封装在一个类中,进程把资 ...
- pyhton锁机制,进程池
第一,进程锁,本来进程是各自的,本不要加锁,但是在屏幕上输出打印时为了防止混乱,在进程模块有一个锁函数,使用如下,需要说明是在py3以上版本没 有出现在过屏幕输出混乱,可以不加,这方法不重要,但是作为 ...
- 使用 swoole_process 实现 PHP 进程池
swoole_process 主要是用来代替 PHP 的 pcntl 扩展.我们知道 pcntl 是用来进行多进程编程的,而 pcntl 只提供了 fork 这样原始的接口,容易使用错误,并且没有提供 ...
- python 进程池的简单使用方法
回到python,用一下python的进程池. 记得之前面试的时候,面试官问:你知道进程池的默认参数吗? 我没有回答上来,后来才知道,是有默认参数的.下面就看看它的默认参数 1. 不加参数 from ...
- 进程池的map方法
from multiprocessing import Process,Pool def f1(n): for i in range(10): n = n+1 if __name__ == ...
- python进程池剖析(三)
之前文章对python中进程池的原理.数据流以及应用从代码角度做了简单的剖析,现在让我们回头看看标准库中对进程池的实现都有哪些值得我们学习的地方.我们知道,进程池内部由多个线程互相协作,向客户端提供可 ...
随机推荐
- 7.json&pickle及软件目录结构规范
json(可以序列化简单数据类型,用于不同语言之间的数据交换传输)import jsonjson.dumps() 写入json.loads() 读取json.dump(info,f) == f.wri ...
- Java - Java 8 新特性
一.Java8新特性 Java8概述:Java8,也就是jdk1.8版本,是意义深远的一个新版本.是Java5之后一个大的版本升级,让Java语言和库仿佛获得了新生. 二.Lambda表达式 Lamb ...
- Android开发Activity全局切换的动画效果
切换动画 slide_left_in.xml 从左边进 --> 退出的时候使用 <?xml version="1.0" encoding="utf-8&quo ...
- 入坑java工程师那些事
最近在知乎上看到好多关于转行做java的朋友,有的在担心学历,有的在想着如何学习java,有的在纠结如何面试.作为一个工作了近10年的java程序员来聊聊我的一些想法,主要从学历.个人能力.工作环境等 ...
- 『动善时』JMeter基础 — 32、JMeter察看结果树组件详解
目录 1.察看结果树介绍 2.察看结果树界面详解 3.察看结果树的其他功能 (1)将数据写入文件中 (2)Search功能 (3)Scroll automatically选项 4.总结 1.察看结果树 ...
- GO语言面向对象01---封装属性与创建对象的方法与工厂模式
package main import "fmt" /* 面向过程编程:调度大量的变量.函数 ---------- 面向对象编程(OOP=Object Oriented Progr ...
- 熬夜讲解vue3组合API中setup、 ref、reactive的用法
1.初识setUp的使用 简单介绍下面的代码功能: 使用ref函数,去使用监听某一个变量的变化,并且把它渲染到视图上. setUp函数是组合API的入口函数.这个是非常重要的. setUp可以去监听变 ...
- CUDA 9中张量核(Tensor Cores)编程
CUDA 9中张量核(Tensor Cores)编程 Programming Tensor Cores in CUDA 9 一.概述 新的Volta GPU架构的一个重要特点是它的Tensor核,使T ...
- 如何使用TensorCores优化卷积
如何使用TensorCores优化卷积 本文将演示如何在TVM中使用TensorCores编写高性能的卷积计划.假设卷积的输入有大量数据.首先介绍如何在GPU上优化卷积. TensorCore简介 每 ...
- Redis系列(五):消息队列
消息队列已经成为现在互联网服务端的标配组件,现在比较常用的消息中间件有RabbitMQ.Kafka.RocketMQ.ActiveMQ.说出来你可能不信,Redis作为一个缓存中间件,居然也提供了消息 ...