php 进程池设计与实现,phper必学!

news2025/1/20 16:25:42

php 进程池设计与实现

    • phper 为什么要学习进程池
    • 池的概念
    • 为什么要有进程池?
      • 动态创建进程缺点
      • 进程池的优点
      • 选择子进程为新任务服务的方式
      • 进程池模型
      • 服务端
      • 客户端
    • 结语

phper 为什么要学习进程池

在php开发过程中经常使用的 php-fpm 使用的进程模型就是进程池,学习进程池知识能让我们更好理解php-fpm 的运行模式,进程池也是php中主流的并发服务器解决方案
在这里插入图片描述
包含我们的 Workerman 也是用的是进程池,编写一个简单的进程池可以帮助我们更好学习Workerman 源码,了解Workerman 为何如此设计
在这里插入图片描述

池的概念

池是一组资源的集合,这组资源在服务器启动之初就完全被创建并初始化,这称为静态资源分配。当服务器进入正式运行阶段,即开始处理客户请求的时候,如果它需要相关的资源,就可以直接从池中获取,无需动态分配

很明显,直接从池中取得所需资源比动态分配资源的速度要快得多,因为分配系统资源的系统调用都是很耗时的。当服务器处理完一个客户连接后,可以把相关的资源放回池中,无需执行系统调用来释放资源。从最终效果来看,池相当于服务器管理系统资源的应用设施,它避免了服务器对内核的频繁访问。

进程池技术的应用至少由以下两部分组成:

资源进程:预先创建好的空闲进程,管理进程会把工作分发到空闲进程来处理。

管理进程:管理进程负责创建资源进程,把工作交给空闲资源进程处理,回收已经处理完工作的资源进程。

为什么要有进程池?

动态创建进程缺点

操作系统繁忙时会有成千上万的任务需要被执行,闲时可能只有零星任务。
那么在成千上万个任务需要被执行的时候,我们就需要去创建成千上万个进程么?

首先,动态创建进程(或线程)是比较耗费时间的,这将导致较慢的客
户响应

即便开启了成千上万的进程,操作系统也不能让他们同时执行,这样反而会影响程序的效率。

动态创建的子进程(或子线程)通常只用来为一个客户服务(除非我们做特殊的处理),这将导致系统上产生大量的细微进程(或线程)。进程(或线程)间的切换将消耗大量CPU时间。

动态创建的子进程是当前进程的完整映像。当前进程必须谨慎地管理其分配的文件描述符和堆内存等系统资源,否则子进程可能复制这些资源,从而使系统的可用资源急剧下降,进而影响服务器的性能

因此我们不能无限制的根据任务去开启或者结束进程。

进程池的优点

进程池是由服务器预先创建的一组子进程,这些子进程的数目在3~10个之间(当然,这只是典型情况)。具体看服务器配置

进程池中的所有子进程都运行着相同的代码,并具有相同的属性,比如优先级、PGID等。因为进程池在服务器启动之初就创建好了,所以每个子进程都相对“干净”,即它们没有打开不必要的文件描述符(从父进程继承而来),也不会错误地使用大块的堆内存(从父进程复制得到)。

选择子进程为新任务服务的方式

当有新的任务到来时,主进程将通过某种方式选择进程池中的某一个子进程来为之服务。相比于动态创建子进程,选择一个已经存在的子进程的代价显然要小得多。至于主进程选择哪个子进程来为新任务服务,则有两种方式:

第一种:主进程使用某种算法来主动选择子进程。最简单、最常用的算法是随机算法和Round Robin(轮流选取)算法,但更优秀、更智能的算法将使任务在各个工作进程中更均匀地分配,从而减轻服务器的整体压力。

第二种:主进程和所有子进程通过一个共享的工作队列来同步,子进程都睡眠在该工作队列上。当有新的任务到来时,主进程将任务添加到工作队列中。这将唤醒正在等待任务的子进程,不过只有一个子进程将获得新任务的“接管权”,它可以从工作队列中取出任务并执行之,而其他子进程将继续睡眠在工作队列上。

本篇文章使用第一种实现任务调度
当选择好子进程后,主进程还需要使用某种通知机制来告诉目标子进程有新任务需要处理,并传递必要的数据。这边我使用的是ipc消息队列进行父子进程间的通信

进程池模型

在这里插入图片描述

服务端

<?php

namespace php_pool;
class Process
{
    public $_pid;//进程pid
    public $_msqid;//进程通信消息队列msqid
}

class Server
{
    public $_sockFile = "pool.sock";// 定义一个sock 文件名 用于unix域通信
    public $_processNum = 3;//默认进程池启动 worker 进程
    public $_keyFile = "pool.php";

    public $idx;//方便子进程获取进程对象

    public $_process = []; // 进程池进程对象数组
    public $_sockfd; // 存放当前进程sock 实例
    public $_run = true; //运行开关

    public $_roll = 0; //轮询算法参数

    public $exitpid = []; //程序退出,回收子进程

	/**
	* 信号处理函数
	*/
    public function sigExitHandler($signo)
    {
        $this->_run = false;
    }

    public function __construct($num = 3)
    {
        $this->_processNum = $num;
        // 安装 SIGINT 信号  Ctrl+c 触发该信号
        pcntl_signal(SIGINT, [$this, "sigExitHandler"]);
        $this->forkWorker();// 创建一些 worker 进程
        cli_set_process_title('master'); // 设置主进程名 方便查看
        $this->Listen(); // 监听请求
        // 回收 worker 进程,避免僵尸进程
        while (1) {
            $pid = pcntl_wait($status);
            if ($pid > 0) {
                $this->exitpid[] = $pid;
            }
            if (count($this->exitpid) == $this->_processNum) {
                break;
            }
        }
        /** @var Process $p */
        foreach ($this->_process as $p) {
        	// 移除消息队列
            msg_remove_queue($p->_msqid);
        }
        // 主进程退出
        fprintf(STDOUT, "master shutdown\n");
    }
	
    public function forkWorker()
    {
    	// 实例化 worker 进程对象
        $processObj = new Process();
        for ($i = 0; $i < $this->_processNum; $i++) {
            $key = ftok($this->_keyFile, $i);
            $mqsid = msg_get_queue($key);// 创建 worker 通信的消息队列
            $process = clone $processObj; // 克隆 worker 进程对象
            $process->_msqid = $mqsid; 
            $this->_process[$i] = $process;
            $this->idx = $i; // 方便 worker 进程使用进程对象
            /**
             * @var Process $this
             */
            $this->_process[$i]->_pid = pcntl_fork();// 派生 worker 子进程
            if ($this->_process[$i]->_pid == 0) {//子进程逻辑
                $this->Worker(); // 启动 worker 子进程
            } else {//父进程逻辑
                continue;
            }
        }
    }

    public function Listen()
    {
    	// 创建 sock 文件描述符实例
        $this->_sockfd = socket_create(AF_UNIX, SOCK_STREAM, 0);
        // 创建异常判断
        if (!is_resource($this->_sockfd)) {
            fprintf(STDOUT, "socket create fail:%s\n", socket_strerror(socket_last_error($this->_sockfd)));

        }
        // 移除上传遗留的 pool.sock 避免重复绑定错误
        unlink($this->_sockFile);
        // 将sockfd 套接字绑定到文件上
        if (!socket_bind($this->_sockfd, $this->_sockFile)) {
            fprintf(STDOUT, "socket bind fail\n", socket_strerror(socket_last_error($this->_sockfd)));
        }
		// 监听套接字上的连接
        socket_listen($this->_sockfd, 10);
        // 启动事件循环操作
        $this->evenLoop();
    }
	
	// worker 轮询算法实现
    public function selectWorker($data)
    {
        /**
         * @var Process $process
         */
         //轮询获取 worker 进程实例
        $process = $this->_process[$this->_roll++ % $this->_processNum];
        // 获取worker 消息队列 msq_id 
        $msgid = $process->_msqid;
        // 往worker 进程消息队列 投递 请求任务 msg_send 函数设置为非阻塞
        if (msg_send($msgid, 1, $data, true, false)) {
            fprintf(STDOUT, "send ok\n");
        }
    }

    public function evenLoop()
    {
		// 获取 sock 套接字 注册到 socket_select 可读事件
        $readFds = [$this->_sockfd];
	
        $writeFds = [];
        $exFds = [];
        while ($this->_run) {
        	//socket_select 函数就可以对集合$readFds中的数据是否发生可读行为进行监听【可写、异常等 我们暂且不表设置为空】以达到在同一个进程中实时处理多个IO的目的 接受套接字数组并等待它们改变状态。这里我们只监听了可读事件
            $ret = socket_select($readFds, $writeFds, $exFds, null, null);
            \pcntl_signal_dispatch();// 分发信号
   			// socket_select 函数异常直接退出
            if (false === $ret) {
                break;
            } else if ($ret === 0) {//没有事件发生返回 0
                continue;
            }
			
            if($readFds){
                foreach ($readFds as $fd){
                    if($fd == $this->_sockfd){
                    	// 接收客户端请求
                        $connfd = socket_accept($fd);
                        // 读取客户端发来数据
                        $data = socket_read($connfd,1024);
                        if($data){
                        	// 选择一个 worker 来处理 客户端请求
                            $this->selectWorker($data);
                        }
                        // 给客户端 一个响应
                        socket_write($connfd,"ok",2);
                        // 关闭客户端连接
                        socket_close($connfd);
                    }
                }
            }
        }
       	// 主进程退出
        // 关闭 sock 实例
        socket_close($this->_sockfd);
        /**
         * @var Process $p
         */
		// 给所有 worker 进程发送进程退出消息
        foreach ($this->_process as $p) {
            if (msg_send($p->_msqid, 1, 'quit')) {
                fprintf(STDOUT, "master send quit ok\n");
            }
        }

    }

	// 处理业务逻辑 worker 进程,具体流程由业务逻辑决定
    public function Worker()
    {
        fprintf(STDOUT, "child pid=%d start\n", posix_getpid());
        /**
         * @var Process $process
         */
        $process = $this->_process[$this->idx];
        $msgid = $process->_msqid;
        while (1) {
        	// 获取主进程投递过来的客户端请求消息
            if (msg_receive($msgid, 0, $msgType, 1024, $msg)) {
                fprintf(STDOUT, "child pid =%d recv:%s\n", posix_getpid(), $msg);
                // 监听退出命令
                if (strncasecmp($msg, 'quit', 4) == 0) {
                    break;
                }
            }
        }

        fprintf(STDOUT, "child pid=%d chutdown\n", posix_getpid());
        exit(0);
    }
}

(new Server(3));

客户端

<?php

namespace php_pool;
$sockFile = "pool.sock";
$_sockfd = socket_create(AF_UNIX, SOCK_STREAM, 0);
// 连接服务端
if(socket_connect($_sockfd,$sockFile)){
        $data = 'hello';
        // 发送请求消息给服务端
        socket_write($_sockfd,$data,strlen($data));
        // 接收服务端响应
        echo socket_read($_sockfd,1024)."\n\r";
        //关闭连接
        socket_close($_sockfd);
}

首先我们运行服务端代码,服务运行后进程池有3个 worker 进程,没问题
在这里插入图片描述
接下来我们运行客户端代码,发个 hello 消息给服务端,让它给我们处理一下,多次运行服务端消息接收正常,服务端每次都轮询一个进程池里的worker 进程为我们处理任务
在这里插入图片描述
最后测试服务端退出,所有 worker 进程 正常退出 主进程回收完子进程资源也正常退出,不过 socket_select 函数报了警告,不过不用理会,这个是我们按下 ctrl+c 导致系统调用中断导致的警告,可以忽略
在这里插入图片描述

结语

进程池的设计根据业务不同写法会有所差异,但大致流程都差不多,本次编写的进程池也只是学习使用,并不特别完善,如果有问题请联系我改正谢谢!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/40931.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何基于FSM有限状态机实现Enemies AI

文章目录&#x1f35f; Preface&#x1f355; 巡逻状态&#x1f37f; 寻路状态&#x1f32d; 攻击状态&#x1f357; 完整代码&#x1f35f; Preface 本文简单介绍如何基于FSM有限状态机实现Enemies AI&#xff0c;首先定义敌人的AI逻辑&#xff1a;默认状态下Enemy为巡逻状态…

刷爆力扣之等价多米诺骨牌对的数量

刷爆力扣之等价多米诺骨牌对的数量 HELLO&#xff0c;各位看官大大好&#xff0c;我是阿呆 &#x1f648;&#x1f648;&#x1f648; 今天阿呆继续记录下力扣刷题过程&#xff0c;收录在专栏算法中 &#x1f61c;&#x1f61c;&#x1f61c; 该专栏按照不同类别标签进行刷题&…

使用 nlohmann 解析 json 文件

使用 nlohmann 解析 json 文件nlohmann/json的配置json基本数据结构json文件的读取、构造与输出C对象与nlohmann::json对象的转换C对象转换成nlohmann::json对象nlohmann::json对象转换成C对象序列化反序列化序列化nlohmann 是德国工程师&#xff0c;以其名字为工程名的 nlohm…

springboot项目的打包发布部署,jar和war的区别

简介&#xff1a; 1.Spring Boot使用了内嵌容器&#xff0c;因此它的部署方式也变得非常简单灵活&#xff0c;可以将Spring Boot项目打包成JAR包来独立运行&#xff0c;也可以打包成WAR包部署到Tomcat容器中运行&#xff0c;如果涉及大规模的部署&#xff0c;Jenkins成为最佳选…

【HCIP-Datacom】 IS-IS基础 ISIS动态路由协议配置(ISIS思维导图在底部)

目录 ISIS配置方法&#xff1a; 路由计算&#xff1a; ATT置位条件&#xff1a; 路由渗透&#xff1a; ISIS的认证&#xff1a; ISIS配置命令&#xff1a; ISIS的开销类型&#xff1a; ISIS配置方法&#xff1a; 进入ISIS进程 isis 1 //创建isis进程 设置实体名 network-entit…

.NET 升级发布后,IIS出现了System.IO.DirectoryNotFoundException

最近计划升级项目到.NET6, 在使用Release发布后发现IIS不能发现wwwroot目录,什么错误? 📢欢迎点赞 :👍 收藏 ⭐留言 📝 如有错误敬请指正,赐人玫瑰,手留余香!📢本文作者:由webmote 原创📢作者格言:无尽的折腾后,终于又回到了起点,工控,我来了 !1 发布的一…

《统计学习方法》 第十六章 主成分分析PCA

主成分分析(PCA) 假设xxx为mmm 维随机变量&#xff0c;其均值为μ\muμ&#xff0c;协方差矩阵为Σ\SigmaΣ 考虑由mmm维随机变量xxx到mmm维随机变量yyy的线性变换 yiαiTx∑k1mαkixk,i1,2,⋯,my _ { i } \alpha _ { i } ^ { T } x \sum _ { k 1 } ^ { m } \alpha _ { k …

计算点在线上的投影坐标

如题 计算点到线上的垂点&#xff0c;首先明确&#xff1a; 该线段必须给出确切的起始点和终点&#xff0c; 而不是一个向量&#xff0c;因为一个向量并不能代表一个线段。 所以参数列表如下&#xff1a; Vector3 VerticalPoint(Vector3 point, Vector3 lStart, Vector3 lEnd…

【论文翻译】增强复制状态机的两阶段提交协议

Enhancing Two Phase-Commit Protocol for Replicated State Machines Halit Uyanık and Tolga Ovatman Department of Computer Engineering Istanbul Technical University 34469 Istanbul, Turkey Email目录1 介绍2 设计和实现2.1 事件类型2.2 在状态机上执行事件2.3 具有优…

8、常用基本命令(重要)

文章目录8、常用基本命令&#xff08;重要&#xff09;8.1 帮助命令8.1.1 man 获得帮助信息8.1.2 help 获得 shell 内置命令的帮助信息8.1.3 常用快捷键8.2 文件目录类8.2.1 pwd 显示当前工作目录的绝对路径8.2.2 ls 列出目录的内容8.2.3 cd 切换目录8.2.4 mkdir 创建一个新的目…

Linux驱动入门

一、驱动简介 Linux的驱动在本质上就是一种软件程序&#xff0c;上层软件可以在不了解硬件特性的情况下&#xff0c;通过驱动提供的接口&#xff0c;和计算机硬件进行通信。 系统调用是内核和应用程序之间的接口&#xff0c;而驱动程序是内核和硬件之间的接口。它为应用程序屏蔽…

缓存穿透、缓存击穿、缓存雪崩及其解决方案

缓存&#xff08;cache&#xff09;&#xff0c;大家都非常熟悉&#xff0c;几乎每个系统乃至整个计算机体系中都会用到。在分布式系统架构中&#xff0c;主要用于减轻数据库的压力&#xff0c;提高系统的响应速度和并发吞吐&#xff0c;即空间(内存)换时间。当大量的读、写请求…

【模型推理加速系列】06: 基于resnet18加速方案评测

简介 花雪随风不厌看&#xff0c;更多还肯失林峦。愁人正在书窗下&#xff0c;一片飞来一片寒。小伙伴们好&#xff0c;我是微信公众号小窗幽记机器学习的首席称重师&#xff1a;卖麻辣烫的小男孩。今天这篇文章以resnet18模型为例&#xff0c;对比Pytorch、ONNX、TorchScript…

cmdline(二):uboot cmdline怎么传?cmdline kernel怎么用?

前面我们知道了cmdline是什么&#xff0c;已经在哪里添加cmdline&#xff1f;现在我们来看看在哪里传输cmdline&#xff0c;以及传输收到后怎么用&#xff1f; 参考内容来自前辈&#xff0c;感激&#xff1a; https://blog.csdn.net/weixin_42031299/article/details/12123950…

Spring Boot JPA EntityManager实体管理器示例

在本教程中&#xff0c;您将了解如何在 Spring Boot 示例中使用 JPA EntityManager&#xff08;使用 CRUD 操作和查询方法&#xff09;。我将向您展示&#xff1a; 在 Spring 引导中访问 JPA 实体管理器的方法如何使用实体管理器方法&#xff1a;执行SQL查询使用和CRUD操作cre…

【Android App】实现在线语音合成功能(使用云知声平台和WebSocket 超详细 附源码)

需要源码和Jar包请点赞关注收藏后评论区留下QQ~~~ 一、在线语音合成 虽然国产智能机大多集成了中文语音引擎&#xff0c;但是系统自带的语音工具无法满足商用要求&#xff0c;功能单一&#xff0c;所以势必引入第三方的语音引擎&#xff0c;依靠第三方提供的开发包统一支撑语音…

【新知实验室】——腾讯云音视频TRTC体验

腾讯实时音视频 TRTC 是什么&#xff1f; 腾讯实时音视频&#xff08;Tencent Real-Time Communication&#xff0c;TRTC&#xff09;将腾讯21年来在网络与音视频技术上的深度积累&#xff0c;以多人音视频通话和低延时互动直播两大场景化方案&#xff0c;通过腾讯云服务向开发…

clickHouse基础语法

clichouse数据类型 整形 lnt8 8bit,1字节 &#xff08;-128-127&#xff09;lnt16 16bitlnt32 32bitlnt64 64bit 无符号整型 相比于上面&#xff0c;就是把负数部分挪到正数部分 Ulnt8 &#xff08;0-255&#xff09;Ulnt16Ulnt32Ulnt64 浮点型 Float32 也就是floatFloa…

Instant Neural Graphics Primitives with a Multiresolution Hash Encoding以及源码浅析

背景 现存的一些新视图合成的训练过程和渲染速度都比较慢&#xff0c;其原因是因为query point需要使用MLP编码&#xff0c;而且在一个采样空间中&#xff0c;存在很多无效的query point也要计算其density和color&#xff0c;从而出现很多冗余计算。 作者针对这个问题&#x…

MAUI 中使用 DI 及 MVVM

MAUI 中使用 DI 及 MVVM为什么要使用 依赖注入 和 MVVM如何在 MAUI 中使用依赖注入如何使用 MVVM不使用框架或组件定义一个 BaseViewModelMainViewModel 的实现MainPage 中进行 Binding使用组件优化前面的 ViewModel 代码基项目的效果为什么要使用 依赖注入 和 MVVM MVVM 和 依…