Reactor、epoll、select、poll 到底是什么关系
这题特别适合放在 Swoole、Netty、Nginx、Go 网络模型、高并发服务这类面试里讲。
很多人会背:
- Reactor 是事件驱动
- epoll 很高性能
- select / poll / epoll 都是 IO 多路复用
这些都没错,但如果不能把它们串起来,回答还是会显得碎。
这份的目标就是把下面几件事讲成一条线:
- Reactor 具体在做什么
fd到底是什么- epoll 底层原理怎么理解
- select / poll / epoll 到底差在哪
- 这些东西为什么总是和高并发长连接放在一起讲
一、先给结论
最短的一句话是:
Reactor 是事件分发模型,epoll 是 Linux 下常见的底层实现手段,select / poll / epoll 都是 IO 多路复用机制,但 epoll 更适合高并发连接场景。
如果再展开一层:
- Reactor 负责把“谁有事件”这件事和“谁去处理业务”拆开
- epoll 负责高效告诉 Reactor:哪些 fd 就绪了
- select / poll 也能做这件事,但连接多的时候开销更大
二、fd 到底是什么
fd 是 file descriptor,文件描述符。
在 Linux 里,不只是文件有 fd,socket 也有 fd。
所以在网络服务里,一个连接、一个 socket,本质上都可以用 fd 来表示。
当我们说:
- 某个 fd 可读
- 某个 fd 可写
本质上是在说:
- 这个 socket 现在可以读,不容易阻塞
- 这个 socket 现在可以写,不容易阻塞
可读不等于什么
可读不等于:
- 业务消息一定完整了
它更接近:
- 现在这个 socket 的接收缓冲区里有数据了
可写不等于什么
可写不等于:
- 你一写就一定全部发完
它更接近:
- 发送缓冲区目前有空间,可以继续写
三、Reactor 具体是干嘛的
Reactor 不是业务处理器,更像:
- 事件分发器
- 调度层
- 网络事件总控
它主要做 4 件事:
- 维护一批自己关心的 fd
- 告诉内核自己关心这些 fd 的哪些事件
- 等待哪些 fd 变成可读、可写、异常、超时
- 把事件分发给对应的 handler / worker
所以你可以把 Reactor 理解成:
- 它不负责把业务做完
- 它负责发现谁有事了,然后把活派出去
四、为什么要把 IO 监听和业务处理拆开
这块是 Reactor 模型最核心的价值。
如果不拆,会发生什么?
假设同一个线程既负责:
- 监听所有连接有没有事件
- 又负责查 MySQL、调 Redis、执行业务代码
那一旦它进入慢业务,比如:
- 慢 SQL
- 慢 HTTP
- 重 CPU 计算
这段时间它就没空继续盯其他连接了。
结果就是:
- 新请求进来没人及时接
- 已经连着的 fd 就绪了也没人及时处理
- 整个系统吞吐会被慢业务拖住
所以 Reactor 模型强调的是:
谁负责盯事件,谁负责跑业务,尽量不要是同一条执行路径。
这也是为什么经常会说:
Reactor 把 IO 事件监听和业务处理拆开。
五、Reactor 底层为什么常和 epoll 一起出现
因为 Reactor 只是设计思想,不是某个 Linux 系统调用。
它要真正落地,就得依赖底层机制告诉它:
- 哪些 fd 就绪了
在 Linux 下,最常见的就是:
selectpollepoll
其中高并发服务里最常见的是:
epoll
所以常见关系是:
Reactor 用 epoll 来高效拿到就绪事件,再把事件分发给业务处理层。
六、epoll 的原理到底怎么理解
你先记最核心的一句:
epoll 不是每次自己去扫所有 fd,而是先把关注的 fd 注册给内核,等 fd 真就绪了,内核再把结果返回给你。
它大致有两层集合
经典面试答法里,经常这么讲:
- 红黑树:管理“我关注哪些 fd”
- ready list:管理“哪些 fd 已经就绪”
常见调用链
1. epoll_create
创建一个 epoll 实例。
你可以把它理解成:
- 创建一个事件监听器
2. epoll_ctl
把 fd 加进去、删掉、改关注事件。
比如:
- 关注某个 socket 的可读事件
- 或者同时关注可写事件
3. epoll_wait
阻塞等待事件发生。
一旦某些 fd 就绪,它就把结果返回给你。
更底层一点怎么讲
- 你先把 fd 和关心的事件注册到内核
- 某个 socket 状态发生变化,比如收到数据
- 内核发现这个 fd 现在可读了
- 内核把它挂到 ready list
- 如果用户线程正阻塞在
epoll_wait,就把它唤醒 - 用户态拿到就绪 fd,再自己去
read / write
所以要注意:
epoll 返回的不是数据本身,而是“现在这个 fd 可以读 / 写了”的通知。
七、select、poll、epoll 到底差在哪
这题最稳的主线就是:
它们都在做 IO 多路复用,但把 fd 交给内核、等待事件、拿回结果的方式不同。
1. select
特点:
- 用位图管理 fd
- 通常有数量上限
- 每次调用都要把 fd 集合从用户态拷到内核
- 内核处理后,用户态还要再全量扫描结果
最常见的问题:
- fd 数量上限明显
- 连接多时全量扫描成本高
2. poll
特点:
- 用数组描述 fd
- 去掉了
select的固定 fd 数量上限 - 但本质仍然是线性扫描
所以它比 select 更灵活,但不是性能模型的大升级。
3. epoll
特点:
- 先用
epoll_ctl把关注的 fd 注册到内核 - 关注集合常驻在内核里
epoll_wait返回的是已经就绪的 fd
所以高并发连接场景里,它通常更高效。
一句话对比
select:有上限,每次全量扫描poll:没固定上限,但还是全量扫描epoll:fd 常驻内核,只返回就绪 fd
八、为什么大家总说 epoll 更适合高并发
因为高并发服务经常是这种情况:
- 挂着 10 万个连接
- 但同一时刻真正活跃的可能只有一小部分
这时候:
select / poll还是要把大量 fd 扫一遍epoll更接近只处理真正 ready 的那部分 fd
所以优势主要来自:
- 不用每次重复传整个 fd 集合
- 不用每次全量扫描所有 fd
- 更贴近“谁就绪处理谁”的事件驱动方式
九、LT 和 ET 是什么
这块不是你今天最先问的,但和 epoll 经常绑在一起追问。
LT
Level Triggered,水平触发。
意思是:
- 只要 fd 里还有数据没处理完
- 你下一次等事件时还会继续提醒你
特点:
- 更容易写
- 不容易漏事件
ET
Edge Triggered,边缘触发。
意思是:
- 只有状态从“没就绪”变成“就绪”的那一下提醒你
如果你没把数据读干净,后面可能就不再提醒。
所以 ET 通常要求:
- fd 设成非阻塞
- 一次读到
EAGAIN为止
一句话记忆:
- LT:还有数据就会继续提醒
- ET:只在状态变化那一下提醒
十、把这条线挂回 Swoole,怎么讲
你可以这样串起来:
- Swoole 先把 PHP 从短生命周期脚本模型拉到常驻服务模型
- 长连接和高并发下,关键问题变成“怎么高效盯住大量连接”
- 所以会引入 Reactor 这种事件分发思路
- 在 Linux 下,Reactor 常用 epoll 这类机制高效获取就绪 fd
- Reactor 拿到事件后,再把业务分给 Worker 去处理
所以它的重点不是“一个词叫 Reactor”,而是:
把监听连接和跑业务拆开,再用 epoll 这类机制把“谁就绪了”这件事做得足够高效。
十一、面试里怎么讲会更像做过的人
你可以这样答:
我理解 Reactor 不是业务处理器,而是事件分发器。它负责盯住大量 fd 的可读、可写、超时事件,再把这些事件分发给真正跑业务的 Worker。它底层在 Linux 下经常会和 epoll 一起出现,因为 epoll 能把关注的 fd 注册到内核里,等 fd 真就绪了再返回 ready 结果,这比 select、poll 这种每次全量扫描的方式更适合高并发连接场景。所以在 Swoole、Nginx、Netty 这类系统里,这几个概念经常会连在一起讲。
十二、30 秒版怎么答
Reactor 是事件分发模型,本质上负责监听大量 fd 的可读可写事件,然后把事件分发给业务处理层。epoll 是 Linux 下常用的底层实现机制,它会把关注的 fd 注册到内核里,只返回真正就绪的 fd,所以比 select 和 poll 更适合高并发连接场景。
十三、1 分钟版怎么答
我会把这几个概念串起来理解。Reactor 负责把“谁有事件”和“谁去处理业务”拆开,避免同一条执行路径既要盯连接又要跑慢业务。它底层在 Linux 下常常靠 epoll 来实现。epoll 的核心思路是把关注的 fd 常驻在内核里,谁就绪了就把谁放到 ready list,再由 epoll_wait 返回给用户态处理。相比之下,select 和 poll 每次都要把 fd 集合交给内核并全量扫描,所以连接数大时效率会差很多。
十四、3 分钟版怎么答
我理解 Reactor、epoll、select、poll 其实是一条线上的不同层次。Reactor 是设计模型,解决的是高并发网络服务里“监听连接”和“处理业务”不要堵在同一条路径上的问题;fd 是 Linux 里对文件和 socket 的统一抽象,所以网络连接本质上也是 fd。要让 Reactor 真正知道哪些 fd 有事件,就要依赖 IO 多路复用机制,比如 select、poll、epoll。
select 最老,通常有 fd 数量上限,而且每次调用都要全量扫描;poll 去掉了固定上限,但本质还是线性扫描;epoll 则是先把关注的 fd 注册到内核里,由内核维护关注集合和 ready list,等某个 fd 变成可读可写时,再把结果返回给用户态。所以在高并发连接、长连接、WebSocket、网关这些场景里,epoll 更适合。
如果把它挂回 Swoole 来讲,就是:Swoole 通过 Reactor + Worker 的分工,把网络事件监听和业务执行拆开,再通过 epoll 这类机制高效拿到就绪事件,这样一个线程或进程就能盯住大量连接,而不是一个连接一个线程死等。