面试知识库
极高 进阶

IO多路复用#

一句话答案#

一个线程同时监听多个 IO 事件(fd),哪个就绪处理哪个,避免每连接一线程,实现有 select/poll/epoll。

核心要点

核心思想: 不阻塞在单个 IO 上,而是同时监听多个 fd,哪个就绪处理哪个

三种实现: select(fd_set 位图,fd 数值须 < 1024) → poll(pollfd 数组,无固定上限) → epoll(红黑树+就绪链表,最高效)

面试回答(2分钟版)

IO多路复用的核心思想是用一个线程同时监听多个文件描述符的IO事件,哪个就绪就处理哪个,避免了传统BIO模型中每个连接都需要一个线程阻塞等待的问题。Linux下有三种实现:select最早出现,用fd_set位图存储fd,只能监听数值小于FD_SETSIZE(1024)的fd,每次调用需要把fd集合拷贝到内核并O(n)遍历检查就绪状态;poll用pollfd结构体数组替代位图去掉了这个限制,但同样每次调用都要整体拷贝进内核,但遍历效率同样是O(n);epoll是最优方案,用红黑树管理fd注册一次即可,事件就绪通过回调加入就绪链表,epoll_wait直接返回就绪fd列表,效率O(1)。需要特别强调的是IO多路复用属于同步IO模型,因为数据从内核拷贝到用户空间这一步仍然是同步阻塞的,真正的异步IO是Linux的io_uring(5.1+;老的内核AIO基本只对O_DIRECT有效)或Windows的IOCP,内核完成所有操作后通知用户进程。Java NIO的Selector底层在Linux上就是epoll实现,Redis和Nginx的高并发能力都依赖IO多路复用。

追问与易错

追问方向:

  • “IO 多路复用是同步还是异步?”→ 是同步 IO,因为数据从内核拷贝到用户空间仍需进程自己完成(阻塞等待);真正的异步 IO 是 io_uring / 内核 AIO / IOCP,内核全程搞定后通知进程
  • “select/poll/epoll 区别?”→ select 有 1024 fd 限制且 O(n) 遍历,poll 无限制但仍 O(n),epoll 红黑树 + 回调就绪链表 O(1) 最高效
  • “Java NIO 用的是哪个?”→ Java NIO 的 Selector 在 Linux 上底层用 epoll,macOS 上用 kqueue,Windows 上 JDK 17 起默认用基于 wepoll 的实现(之前是 select)(JDK 实现自动适配平台)

易错点:

  • ❌ IO 多路复用是异步 IO——不是,是同步 IO:进程阻塞在 select/epoll_wait 上等就绪,读数据仍要自己调 read
  • ❌ 能用来异步读写普通磁盘文件——普通文件对 select/poll 永远报”就绪”,epoll_ctl 加普通文件直接返回 EPERM;多路复用适用于 socket、管道、终端、eventfd/timerfd 等,磁盘文件异步要用 io_uring