1. 从单兵作战到团队协作:为什么我们需要池化技术?
上一篇文章我们聊了单线程Epoll配合非阻塞I/O搭建的简易echo server,那感觉就像是一个超级能干的快递员,一个人骑着电动车在城里来回穿梭,处理所有包裹。刚开始业务量小,他忙得过来,甚至还有点闲。但随着公司名气变大,包裹量暴增,这个快递员就算不吃不喝不睡,也绝对处理不完,最终的结果就是大量包裹积压,客户投诉电话被打爆。
我们的WebServer也是一样。当你的服务只有几十个并发连接时,单线程Epoll模型简洁高效,完全够用。但面对成百上千,甚至上万的并发请求时,单个线程(或进程)的CPU计算能力和I/O处理能力很快就会达到瓶颈。这时候,我们就需要引入“团队协作”的概念,也就是进程池和线程池。
你可以把“池”想象成一个预先组建好的专业团队。在服务器启动时,我们就创建好一定数量的工作进程或工作线程,它们整装待发。当一个新的网络连接请求到来时,主进程(或主线程,也就是那个“经理”)不再自己亲自去处理这个连接的后续所有读写操作,而是从“池子”里挑选一个空闲的“员工”(子进程或子线程),把这个连接交给他去全权负责。这个员工会用自己的Epoll实例来监听这个连接上的读写事件,并进行处理。
这样做的好处太多了。首先,它充分利用了多核CPU。现代服务器都是多核的,单线程程序只能跑满一个核心,其他核心都在围观摸鱼。而池化技术可以让多个进程或线程并行运行,真正把硬件性能榨干。其次,它避免了频繁创建销毁进程/线程的巨大开销。创建进程(fork)或线程(pthread_create)是操作系统级别的重量级操作,非常耗时。池化技术相当于我们提前招好了正式员工,避免了每次来活都临时去劳务市场找零工(创建)和干完活就辞退(销毁)的折腾。最后,它通过限制“员工”数量,实现了资源的可控管理。无限制地创建线程会导致内存耗尽,过多的进程上下文切换也会拖垮系统。池的大小就是我们设置的一个安全阀。
所以,从单线程Epoll演进到使用池化技术,是WebServer应对高并发场景的必然选择。接下来,我们就亲手拆解一下这两种不同的团队管理模式:进程池和线程池。
2. 进程池实战:用管道指挥一支fork出来的军队
我最早实现的就是进程池模型,因为它概念上相对直白。思路是这样的:一个主进程负责监听新的客户端连接(listenfd),它有一队用fork()创建出来的子进程。那么问题来了,主进程怎么通知子进程:“嘿,来新活了,快去accept”?
这就需要进程间通信(IPC)。我当时选择了管道(pipe),更准确地说,用了socketpair()创建了全双工的管道。每个子进程都有一根“专属对讲机”连着主进程。主进程的epoll只监听listenfd,一旦有新的连接到来,它就通过轮询(round-robin)算法选一个子进程,然后往对应的管道里写一个消息。子进程的epoll则监听着自己那端的管道,读到消息后,就知道该自己去调用accept()接收这个新连接了。
来看看我当时写的核心代码结构,我把它简化成一个模板类:
template <typename T>
class processpool {
private:
int listenfd;
int process_number; // 进程数量
process* sub_process; // 进程信息数组
static processpool<T>* instance; // 单例模式
public:
static processpool<T>* create(int listenfd, int number = 8) {
if (!instance) {
instance = new processpool(listenfd, number);
}
return instance;
}
void run() {
if (当前是父进程) {
run_parent();
} else {
run_child();
}
}
private:
void run_parent() {
// 父进程epoll只监听listenfd
epoll_event events[MAX_EVENT_NUM];
while (1) {
int ret = epoll_wait(epollfd, events, MAX_EVENT_NUM, -1);
for (int i = 0; i < ret; ++i) {
if (events[i].data.fd == listenfd) {
// 新连接到来,选一个子进程
int target_child = ...; // 轮询算法
// 通过管道发

——从进程池到线程池:Epoll与高效并发模型实战&spm=1001.2101.3001.5002&articleId=154897670&d=1&t=3&u=9dbd502954d64688a0273d19a42cf917)
341

被折叠的 条评论
为什么被折叠?



