深入剖析WebServer(二)——从进程池到线程池:Epoll与高效并发模型实战

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 = ...; // 轮询算法
                    // 通过管道发
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值