这是操作系统内核、网络编程、Redis/Nginx/Reactor 模型中最经典的基础设施问题。 面试官问它,是想评估你对:
- Linux IO 模型
- 内核事件通知机制(epoll)
- Reactor 单线程高性能的原因
- 网络程序为什么用 select/epoll
这些核心基础的理解。
问题核心:
- IO 多路复用(I/O Multiplexing)到底是什么?
- 所谓“多路”指的是什么?
- 所谓“复用”又复用了什么?
只要你能把这三个点打透,面试官会认为你基础扎实。
IO 多路复用就是:一个线程同时监控多个 IO 事件(socket),一旦任何一个就绪,就能被内核通知,从而处理对应的 IO。
更通俗点:
不用为每个连接开线程,也不用阻塞等待某一个 IO,而是用一个线程观察所有连接,一旦谁准备好了就处理谁。
这就是 Nginx、Redis、Netty 都能用单线程支撑十万连接的核心能力。
多路指的是:
- 多个 socket
- 多个文件描述符(fd)
- 多个网络连接
- 多个 input channel
举例:
你有 10000 个 TCP 连接,每条连接都是一“路 IO”。 在没有 IO 多路复用时,要么:
- 每个连接一个线程(线程爆炸)
- 或者循环一个个 poll(CPU 白忙活)
有了 IO 多路复用:
你只需要一个线程就能高效监听所有 10000 个连接是否有事件。
所以:
多路 = 多个 IO 通道(多个 socket/fd)。
最经典的答案:
复用的是一个线程(或一个等待点)来处理多个 I/O。
也可以说:
- 复用 一个阻塞点
- 复用 一个内核事件等待机制(epoll_wait)
- 复用 一个线程上下文
在没有 IO 多路复用时:
socket A read 阻塞
socket B read 阻塞
socket C read 阻塞
……
你需要开多个线程。
现在用 epoll:
epoll_wait() 阻塞一次
→ 内核一次性告诉你哪些 socket 就绪
→ 一个线程逐个处理
阻塞的是 epoll_wait 而不是具体 socket。 这就是 “复用” 的本质。
核心在于:
- 不需要每个连接一个线程(线程上下文切换极贵)
- 不需要主动轮询所有连接(CPU 不空转)
- 内核主动事件通知(epoll)非常高效
尤其是 epoll:
- epoll_ctl:注册事件
- epoll_wait:阻塞等待
- 就绪的 fd 才回调(事件驱动)
事件驱动 + 非阻塞 IO + 单线程 是高性能服务器模型的关键。
| IO 模型 | 机制 | 是否高效 | 性能瓶颈 |
|---|---|---|---|
| blocking IO | 读写直接阻塞 | ❌ | 等待阻塞 |
| non-blocking IO | 读不到立即返回 | ❌ | busy loop |
| IO multiplexing(select/poll/epoll) | 一个 wait 监听多个 fd | ✅ | select/poll 存在性能问题 |
| signal driven IO | 信号通知 | 罕用 | 信号复杂 |
| asynchronous IO | 内核全负责 IO | 复杂/不常用 | 实现复杂 |
其中:
- select:fd 数量有限(1024)、每次都要遍历
- poll:无次数限制,但每次都遍历
- epoll:事件驱动,不遍历所有 fd,性能最好
面试说到这里,胜率非常高。
你可以这样说:
“IO 多路复用是让一个线程可同时监控多个 IO 通道的机制。
多路指多个 socket / 文件描述符; 复用指复用同一个线程或阻塞点来等待所有 IO 事件。
它的本质是:通过 select/poll/epoll 这种内核提供的事件通知机制,让我们不再为每个连接创建线程,也不需要轮询所有连接,而是让内核在某个 socket 可读可写时一次性通知我们。
特别是 epoll 的事件驱动模式,让 Nginx、Redis、Netty 等可以用少量线程处理大量连接,这也是高性能网络服务器的基础。”