LOGO 首页 OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 技术文档 其他文档  
 
网站管理员

Linux epoll 是如何一步步被发明出来的?

admin
2026年9月9日 11:0 本文热度 139

一台服务器,要同时处理 10 万个客户端连接,怎么做?

最直觉的方案:每个连接开一个线程

10 万个连接,10 万个线程。

行不行?

不行。上篇说了,线程有开销,10 万个线程光栈内存就要几百 GB,服务器直接趴下。

那怎么办?

这就是今天要聊的问题。从最原始的方案出发,一步步把 epoll 推导出来。

第一步:最朴素的想法——轮询

服务器有 10 万个连接,不知道哪个连接来数据了。

最笨的办法:挨个问

while (true) {
    for (int i = 0; i < 100000; i++) {
        if (连接[i] 有数据) {
            处理数据;
        }
    }
}

一个 for 循环,把所有连接挨个检查一遍,有数据就处理。

问题显而易见:

99.99% 的时间,大多数连接都没数据。 你把 10 万个连接全问一遍,绝大多数是白问,CPU 全浪费在"有没有数据?没有。有没有数据?没有。"这种无意义的轮询上。

而且是忙等——CPU 一直在转,根本没法休息,一个核跑满了但什么有用的事没干。

必须有更好的方法。

第二步:select——让内核帮你"盯着"

1983 年,BSD Unix 引入了 select

核心思路是:别自己轮询,把连接列表交给内核,让内核帮你盯着,有数据了再通知你。

fd_set read_fds;
FD_ZERO(&read_fds);
FD_SET(fd1, &read_fds);
FD_SET(fd2, &read_fds);
// ... 把所有连接加进去

// 把集合交给内核,最多等5秒
select(max_fd + 1, &read_fds, NULLNULL, &timeout);

// select 返回了,说明有连接就绪了
// 但是哪个?不知道,自己遍历找
for (int i = 0; i <= max_fd; i++) {
    if (FD_ISSET(i, &read_fds)) {
        // 这个连接有数据,处理它
    }
}

select 的工作流程:

select 比纯轮询好多了,至少 CPU 可以在等待期间休眠。

但它有三个硬伤:

① fd 数量上限 1024。fd_set 本质是个位图,固定大小,最多只能监控 1024 个连接。10 万个连接?直接 GG。

② 每次调用都要把 fd 集合从用户空间拷贝到内核。 10 万个 fd,每次调用都要拷贝一大堆数据,开销不小。

③ 内核返回后不告诉你是哪个 fd 就绪了。 你还得自己遍历一遍,O(n) 的开销跑不掉。

第三步:poll——解决了上限,其他问题还在

1997 年,poll 出现了,主要针对 select 的第一个问题改进:

struct pollfd fds[100000];
fds[0].fd = fd1;
fds[0].events = POLLIN;
// ... 填入所有连接

poll(fds, 100000-1);  // 没有 1024 的限制了

// 还是要遍历找就绪的
for (int i = 0; i < 100000; i++) {
    if (fds[i].revents & POLLIN) {
        // 处理
    }
}

poll 把 fd_set 位图换成了链表,彻底去掉了 1024 的上限,这是进步。

但另外两个问题一个没解决:

  • 每次调用还是要把整个 fd 数组从用户空间拷贝到内核
  • 返回后还是要自己遍历,找哪个 fd 就绪了

随着连接数增多,这两个问题越来越致命。

我们来想想根本原因在哪——

select 和 poll 每次调用都是"一次性"的。 内核处理完,结果返回,下次调用得重新传一遍所有 fd,内核重新建立监控关系,重头再来。

连接的监控关系明明没有变化,为什么每次都要重新告诉内核一遍?

如果能让内核把这份监控关系"记住",只告诉它变化的部分,是不是就能解决这两个问题?

这就是 epoll 的核心思路。

第四步:epoll——把监控关系存在内核里

2002 年,Linux 2.5.44 引入了 epoll。

epoll 的设计思路彻底不同:在内核里维护一个持久的数据结构,专门存放你关心的 fd 和监控事件。不用每次调用都重传,只需要增删改查。

epoll 的三个接口:

// 第一步:创建一个 epoll 实例(在内核里),返回一个 epfd
int epfd = epoll_create(1);

// 第二步:把感兴趣的 fd 加入 epoll(只需要加一次)
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = fd1;
epoll_ctl(epfd, EPOLL_CTL_ADD, fd1, &ev);  // 增
epoll_ctl(epfd, EPOLL_CTL_DEL, fd2, NULL); // 删

// 第三步:等待事件发生
struct epoll_event events[64];
int n = epoll_wait(epfd, events, 64-1);

// 关键:epoll_wait 直接返回就绪的 fd,不用遍历!
for (int i = 0; i < n; i++) {
    // events[i].data.fd 就是就绪的那个 fd
    handle(events[i].data.fd);
}

epoll 内部有两个关键数据结构:

红黑树:存放所有被监控的 fd,增删查改都是 O(log n)。用 epoll_ctl 加入一次,内核永久记住,不用每次重传。

就绪链表:当某个 fd 有事件发生时,内核通过回调机制,把这个 fd 直接加入就绪链表。epoll_wait 只需要从就绪链表取,有几个就绪就返回几个,O(1),完全不需要遍历所有 fd

第五步:三者的差距,一张图看懂


连接数一万以下,三者差距不大。连接数上 10 万,select 和 poll 直接趴下,epoll 依然游刃有余。

这就是为什么 Nginx、Redis、各种高并发服务器都基于 epoll。

第六步:ET 和 LT——epoll 的两种工作模式

epoll 有两种触发模式,这个面试也经常考:

LT(Level Triggered,水平触发)—— 默认模式

只要 fd 上还有数据没读完,每次调用 epoll_wait 都会通知你。

就像水位报警器:水还在,就一直报警。

安全,不会漏事件,但如果你不及时处理,会被反复通知。

ET(Edge Triggered,边缘触发)

只在 fd 状态发生变化的那一刻通知你一次,之后不再重复。

就像门铃:只有按下的瞬间响,不管你有没有去开门,之后不会再响。

效率更高,减少了重复通知,但你必须在收到通知后一次性把数据全读完,否则后续可能永远不会再被通知。

LT 模式:fd 可读 → 通知 → 没读完 → 下次 epoll_wait 继续通知
ET 模式:fd 可读 → 通知 → 没读完 → 不再通知(直到下次新数据到来)

ET 模式下,必须用非阻塞 IO,配合循环读取,直到返回 EAGAIN(没有更多数据了)为止。

第七步:epoll + 非阻塞 IO + 线程池——高并发服务器的标准架构

有了 epoll,高并发服务器的完整架构就清晰了:

这套架构就是大名鼎鼎的 Reactor 模式,Nginx、Redis、Netty 底层都是这个思路。

一个主线程用 epoll 管理所有连接,几个工作线程处理实际业务,轻松扛住 10 万甚至百万并发。

最后:把整条演化线串起来

原始轮询:CPU 空转,无脑检查所有连接,浪费严重
    ↓ 把检查工作交给内核
select:内核帮你监控,有事件再通知
    ↓ fd 上限 1024,每次拷贝,返回后还要遍历
poll:去掉 1024 上限
    ↓ 拷贝问题和遍历问题依然存在,连接数大了性能崩
epoll:内核持久存储 fd(红黑树)+ 就绪链表
    ↓ 不用重复拷贝,返回直接就是就绪 fd,O(1) 获取
epoll + 非阻塞 IO + 线程池 = Reactor 模式
    ↓ 高并发服务器的标准基础设施

下次面试被问"epoll 和 select 的区别",你不是在背答案,而是在讲一个有因有果的演化故事。


阅读原文:点击这里


该文章在 2026/9/9 11:00:22 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2026 ClickSun All Rights Reserved  粤ICP备13012886号-2  粤公网安备44030602007207号