进程池分析

一般我们是通过动态创建子进程(或者子线程)来实现并发服务器的,这样的缺点

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

(2)动态创建的子进程通常只用来为一个客户服务,这样导致了系统上产生大量的细微进程(或线程)。进程和线程间的切换将消耗大量CPU时间

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

进程池:有服务器预先创建的一组子进程,这些子进程的数目在3~10个之间,

进程池中的子进程:

(1)他们都运行着相同的代码,具有相同的属性,比如优先级,PGID(组识别码)等。

(2)进程池在服务器启动之初就创建好了,所以每个子进程都相对"干净",即它们没有打开不必要的文件描述符(从父进程继承而来)

(3)也不会错误地使用大块的堆内存(从父进程复制得到)

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

(1)主进程使用某种算法来主动选择子进程

(2)主进程和所有子进程通过一个共享的工作队列来实现同步

:子进程都睡眠在该工作队列上,当有新的任务到来时,主进程将任务添加到工作队列中。

这将唤醒正在等待任务的子进程,不过只有一个子进程将获得新任务的“接管权”,它可以从工作队列中取出任务并执行之,而其他子进程将继续睡眠在工作队列上。

需求:

主进程除了选择好子进程以外,还需要使用某种通知机制来告诉目标子进程有新任务需要处理,并传递必要的数据。

最简单的办法:在父子进程之间预先建立好一条管道,然后通过该管道来实现所有的进程间通信(预先定义好协议来规范管道的使用)

(父子线程间就可以直接用全局变量)

处理多客户:

问题1:

监听socket和连接socket是否都由主进程来统一管理?

半同步/半反应堆模式是由主进程统一管理这两种socket的

高效的半同步/半异步模式以及领导者/追随者模式则是由主进程管理所有监听socket,而各个子进程分别管理属于自己的连接socket的。

对于情况(1),主进程接受新的连接以得到连接socket,然后它需要将该socket传递给子进程(对于线程池而言,父线程将socket)传递给子线程是很简单的,因为他们可以很容易的共享该socket而父进程文件描述符的传递就要靠之前学习的知识来实现了)

对于情况(2),子进程自己调用accept来接受新的连接,这样父进程就无需向子进程传递socket,而只需要简单的通知一声"我检测到新的连接,你来接受它。"

半同步/半异步并发模式的进程池

我们将接受新连接的操作放到子进程中,很显然,对于这种模式而言,一个客户连接上的所有任务始终是由一个子进程来处理的。

我看的进程池是用C++实现的,我先说框架,后面我会努力改成C的

类1:子进程类

存放数据:

目标子进程PID

通道m_pipe

类2:进程池类

存放数据:

(1)进程池允许的最大子进程数

(2)每个子进程最多能处理客户数量

(3)epoll最多能处理的事件数

(4)进程池中的进程总数

(5)子进程在池中的序号,从0开始

(6)每个进程都有一个epoll内核事件表,用m_epollfd标识

(7)监听socket

(8)标识是否停止运行

(9)进程指针保存子进程的描述信息

(10)进程池静态实力

函数:

(1)初始化,创建一个线程池

(2)销毁线程池

(3)统一事件源

(4)启动父进程

(5)启动子进程

(6)run启动进程池

3:由于用了epoll所以需要epoll的一系列函数

4:信号处理函数

5:信号添加函数

问题一:如何存放子进程

首先,父子进程之间是一定要通信的,而子进程都有自己的pid那么

可以设置一个结构体

struct process

{

  pid_t pid;

  int pipefd[2];

}

通过这个结构体构造一个数组,然后里面放的就是各个进程,通过进程的pid来找到相应子进程来调用子进程的通道进行通信.

问题二:关于进程池如何唤醒自己相应的子进程

使用的是epoll监听事件,首先确定自己要监听子进程的什么事件然后再注册事件

使用run_child函数去跑第i个子进程,然后再主函数中通过for(epoll_wait返回的是活跃客户端的个数)对活跃的事件进行一一处理

用进程池存放好子进程的pid和管道,有新的链接来的时候就使用池子里的子进程去完成操作

猜你喜欢

转载自www.cnblogs.com/cthon/p/9069703.html