跳到主要内容
约 3 分钟3 张卡

I/O 多路复用

I/O 多路复用让线程同时等待多个对象的就绪状态。本文负责 Linux select、poll、epoll 与事件循环。

1. 面试核心​

先记住这几条,再读正文
  • 关注集合。 select 和 poll 每次提交关注集合,epoll 将集合保存在内核。卡 · 面试核心
  • select 的编号限制。 glibc 的 fd_set 固定支持小于 FD_SETSIZE 的 fd,默认值为 1024。卡 · 关注集合与就绪结果
  • 触发方式。 epoll 默认水平触发,EPOLLET 启用边缘触发。
  • 边缘触发的处理。 边缘触发配合非阻塞读写,处理到 EAGAIN 后再等待。
  • 单次通知。 EPOLLONESHOT 交付一次事件后停用通知,需要 MOD 重新启用。卡 · 单次通知与工作线程

2. 关注集合与就绪结果​

接口集合管理结果与限制
select每次提交 fd_set 位图。返回时改写集合,下一轮需重建。
poll每次提交 pollfd 数组。每项 revents 返回事件,需遍历数组。
epollepoll_ctl 增删改关注对象。epoll_wait 返回本轮就绪项。

select 的 nfds 是最大 fd 加 1。glibc 的 FD_SETSIZE 默认是 1024。编号达到该值即超出 fd_set 范围,即使只监听一个 fd。

poll 使用动态数组,摆脱固定 fd_set 的限制。poll 仍需在每次等待时检查集合。epoll 在连接多、就绪比例低时可减少重复遍历。

epoll 的事件交付和实际读写都有成本。其整体开销随就绪数量及处理工作量增长。

3. 水平触发与边缘触发​

水平触发在对象持续就绪时继续报告。边缘触发报告新的就绪变化。部分读取后直接等待,会把剩余数据留在缓冲区。若没有新的事件,读取就会停滞。

EPOLLET 应配合 O_NONBLOCK。读循环持续到 EAGAIN,写循环持续到发送完成或 EAGAIN。监听 socket 也应循环 accept 到暂不可用。

处理大连接时还需公平调度。若因处理预算用完而提前暂停,应加入应用就绪队列。下一轮主动继续处理,避免只依赖内核的新通知。

4. 单次通知与工作线程​

EPOLLONESHOT 在交付事件后停用该对象的通知。工作线程处理完毕后,用 EPOLL_CTL_MOD 重新启用。它适合协调同一连接的处理责任。

单次通知控制事件派发。连接状态与对象生命周期仍需应用同步。遗漏重新启用会导致后续事件停滞。

5. 使用边界​

可读事件可对应数据、EOF 或错误。应用应调用读取接口区分结果。收到 HUP 时,缓冲区中仍可有未读数据。

多数空闲 TCP socket 满足可写条件。持续关注 EPOLLOUT 会产生空转。写入遇到 EAGAIN 时再订阅可写,发送完后取消关注。

事件循环使用短小处理器。慢 SQL 和耗时计算进入有界工作队列。fd 关闭与编号复用见文件描述符。

6 · 面试题卡片​

本页题目与参考回答

6.1 epoll 和 poll 有什么区别,使用边缘触发时为什么通常要读到 EAGAIN?​

考察重点:关注集合的管理、边缘触发语义与非阻塞读取循环。

相关内容:面试核心、关注集合与就绪结果、使用边界。

参考回答

poll 每次传入并检查完整集合。

epoll 在内核保留集合,并返回就绪项。边缘触发下,部分读取后未必会再有新通知。因此应用用非阻塞循环读到 EAGAIN。提前暂停时,应主动安排剩余处理。

6.2 select 的 1024 限制是连接数上限吗?​

考察重点:描述符编号范围与集合表示。

相关内容:关注集合与就绪结果、单次通知与工作线程。

参考回答

glibc fd_set 要求 fd 小于 FD_SETSIZE,默认值为 1024。

它约束编号范围,而非整台机器的连接总数。即使只监听一个 fd,其编号也必须落在该范围内。poll 和 epoll 使用其他集合表示。

6.3 EPOLLONESHOT 交付一次事件后为什么收不到后续通知?​

考察重点:单次事件派发与重新启用。

相关内容:单次通知与工作线程。

参考回答

EPOLLONESHOT 会在交付后停用该对象的通知。

处理结束时需调用 EPOLL_CTL_MOD 重新启用。它便于把连接暂时交给一个工作者处理。应用仍要协调状态修改和关闭,避免并发误用。

7. 参考资料​