I/O 多路复用笔记
什么是I/O多路复用
I/O多路复用(I/O Multiplexing)是一种**允许单个线程(或进程)监听多个I/O描述符(fd)**上是否就绪(可读/可写/异常)的方法。这种方式可以有效地管理多个I/O流,在高并发服务器场景下尤为重要。
应用场景
- 网络服务器(如HTTP、Chat服务器)
- 高并发Socket连接处理
- 事件驱动编程模型
- GUI事件循环
- Linux文件/管道/信号等I/O集中管理
阻塞、非阻塞、I/O复用对比
模型 | 特点 | 线程数 | 适用场景 |
---|---|---|---|
阻塞I/O | 调用read/write时会阻塞直到数据准备 | 多 | 简单、低并发任务 |
非阻塞I/O | read/write立即返回,轮询状态 | 多 | 响应要求极快场景 |
I/O多路复用 | 单线程监听多个fd是否就绪 | 单 | 高并发网络服务 |
信号驱动I/O | 使用信号机制异步通知I/O完成 | 单 | 使用复杂,少用 |
异步I/O (AIO) | 操作系统完成I/O并通知应用 | 单 | 真正非阻塞,较少用 |
三种常见多路复用方式
1. select
- 最早的I/O多路复用方式
- 使用
fd_set
管理文件描述符集合 - 每次调用都需要重建集合,效率低
- 最大监听fd数受限(通常1024)
2. poll
- 支持任意fd数量(但仍需遍历)
- 使用
pollfd
数组存放监听项 - 没有fd上限,但效率也较低(O(n))
3. epoll
(Linux特有)
- 内核级支持的高效方式
- 支持事件通知机制
- 使用红黑树+链表管理事件
- 支持边缘触发(ET) 和 水平触发(LT)
- 适合大规模连接数的服务
epoll使用流程
int epfd = epoll_create(1); // 创建epoll对象struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = listenfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev); // 添加监听fdstruct epoll_event events[1024];
int n = epoll_wait(epfd, events, 1024, -1); // 等待事件发生for (int i = 0; i < n; i++) {if (events[i].events & EPOLLIN) {// 可读事件处理}
}
ET 与 LT 模式比较
模式 | 全称 | 特点 | 适用 |
---|---|---|---|
LT | Level Trigger | 默认模式,数据未读完会重复触发 | 易用、安全 |
ET | Edge Trigger | 仅在状态变化时触发一次 | 高性能、需非阻塞 |
注意:ET模式下必须使用非阻塞I/O,并在事件触发时循环读写直到EAGAIN
epoll服务器示意
epfd = epoll_create(1);
epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, ...);while (1) {int n = epoll_wait(epfd, events, 100, -1);for (int i = 0; i < n; i++) {int fd = events[i].data.fd;if (fd == listenfd) {int connfd = accept();epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, ...);} else {// 处理连接数据}}
}
性能比较总结
模型 | 遍历方式 | 空间开销 | 扩展性 | 推荐使用 |
---|---|---|---|---|
select | 遍历所有fd | 大 | 差 | ❌ |
poll | 遍历pollfd数组 | 较大 | 一般 | ❌ |
epoll | 事件驱动 | 小 | 高 | ✅ |
常见面试题及答案
select 和 epoll 有什么区别?
答:
对比项 | select | epoll |
---|---|---|
最大连接数 | 受限于FD_SETSIZE(通常1024) | 理论上无上限(受限于内存) |
数据结构 | 数组 | 红黑树 + 链表 |
拷贝开销 | 每次调用需要复制fd_set | 只复制活跃事件 |
性能 | O(n) 遍历所有fd | O(1) 返回活跃事件 |
支持触发模式 | 仅LT | 支持LT和ET |
epoll为什么比select/poll高效?
答:
- select/poll 每次调用都需遍历所有fd,复杂度为O(n)
- epoll 采用内核维护的红黑树/链表结构,注册后由内核监听,用户只需关注活跃事件
- epoll 使用事件驱动机制(epoll_wait 只返回就绪fd)
- 支持边缘触发(ET),避免重复通知,提高性能
epoll的LT和ET模式有何区别?ET模式下为什么要非阻塞?
答:
- LT模式:只要fd可读/可写,就会不断触发事件。适合新手,使用方便。
- ET模式:只有状态发生变化时才触发一次。高性能,但复杂,必须非阻塞并读/写完数据。
- 若ET模式下使用阻塞I/O,可能漏掉后续事件导致连接卡死。
epoll是如何实现通知的?
答:
- 内核中维护一棵红黑树保存监听fd,以及一个就绪链表。
- 当有I/O事件发生,内核把就绪fd放入链表中。
epoll_wait
直接读取就绪链表,避免遍历。- 用户空间与内核之间使用
mmap
共享内存,提升性能。
select/poll还有使用价值吗?
答:
有。尽管效率低,但由于:
- 跨平台(Windows也支持select)
- 简单易用,便于教学和调试
因此在一些小型项目或对性能要求不高的场合仍有使用场景。
总结
- I/O多路复用是高性能服务器编程的关键技术
epoll
是Linux平台推荐使用的高效模型- 精通 epoll 的使用及其 ET 模式是面试加分项
- 面试时注重原理+代码+比较+应用场景