I/O多路复用的三种机制(select、poll、epoll)
select、poll、epoll是用于I/O所路服用的三种机制,常见于Linux系统中,用于高效处理多个文件描述符的I/O事件。
select
select是最早的I/O多路复用机制,允许程序监听多个文件描述符,等待其中一个或多个变为可读、可写或出现异常。
特点:
- 通过
fd_set数据结构管理文件描述符。 - 支持的文件描述符数量有限(通常为1024)。
- 每次调用都需要将
fd_set从用户空间拷贝到内核空间。 - 每次调用都需要遍历所有文件描述符,时间复杂度为O(n)。
- 通过
优点:
- 跨平台支持,几乎所有操作系统都支持了
select。
- 跨平台支持,几乎所有操作系统都支持了
- 缺点:
- 文件描述符数量受限。
- 每次调用都需要拷贝
fd_set,效率低。 - 需要遍历所有文件描述符,性能随文件描述符数量增加而下降。
常用API:
1 | int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); |
poll
poll是对`select的改进,使用pollfd结构体管理文件描述符
特点:
- 使用
pollfd结构体管理文件描述符,没有数量上的限制,但是收到系统资源的限制。 - 每次调用仍然需要将
pollfd数组从用户空间拷贝到内核空间。 - 每次调用都需要遍历所有文件描述符,时间复杂度为O(n)。
- 使用
优点:
- 支持的文件描述符数量没有硬性限制。
- 相比于select更加灵活。
缺点:
- 每次调用都需要拷贝
pollfd,效率低。 - 需要遍历所有文件描述符,性能随文件描述符数量增加而下降。
- 每次调用都需要拷贝
常用API:
1 | int poll(struct pollfd *fds, nfds_t nfds, int timeout); |
epoll
epoll是Linux特有的高校I/O多路复用机制,解决了select和poll的性能问题。
特点:
- 使用红黑树和双链表管理文件描述符,支持高校的事件注册和通知。
- 支持边沿(ET)触发和水平(LT)触发模式。
- 每次调用不需要拷贝所有文件描述符,只需要返回就绪的事件。
- 时间复杂度为O(1),性能不受文件描述符数量影响。
优点:
- 高效处理大量文件描述符,适合高并发场景。
- 事件驱动,只返回就绪的事件,无需便利所有的文件描述符。
- 支持边沿触发模式,减少事件通知次数。
- 缺点:
- 仅支持Linux系统,无法跨平台。
常用API:
1 | int epoll_create(int size); // 创建 epoll 实例 |
事件通知模式/触发机制(LT,ET)
水平触发(Level-Triggerred)和边沿触发(Eage_Triggerred)是I/O多路复用机制中的两种时间通知模式,他们决定了何时通知应用程序文件描述符的状态变化。
水平触发(LT)
默认的事件通知模式,当文件描述符处于就绪状态时,会持续通知应用程序。
当被监控的Socket上有可读事件发生时,服务器端不断地从epoll_wait中苏醒,直到内核缓冲区被read函数读完才结束,没必要一次执行尽可能多的读写操作。
边沿触发(ET)
高效的事件通知模式,只有当文件描述符的状态发生变化时(例如从不可读变为可读),才会通知应用程序。
当被监控的Socket上有可读事件发生时,服务器端只会从epoll_wait中苏醒一次,因此程序需要保证一次将内核缓冲区的数据读取完,因此需要循环的从文件描述符中读取数据,如果文件描述符是阻塞的,没有数据可读写时,进程会阻塞在读写函数那里,因此,边沿触发模式一般搭配非阻塞I/O使用,程序会一直执行I/O操作。
LT和ET对比
| 特性 | 水平触发(LT) | 边缘触发(ET) |
|---|---|---|
| 通知时机 | 只要文件描述符就绪,就持续通知 | 只有当文件描述符状态变化时通知 |
| 事件处理 | 可以分多次处理数据 | 需要一次性处理完所有数据,否则会丢失事件 |
| 效率 | 可能频繁通知,效率较低 | 减少不必要的通知,效率较高 |
| 实现复杂度 | 简单易用 | 复杂,需要更精细的逻辑 |
| 适用场景 | 低并发、对性能要求不高的场景 | 高并发、对性能要求高的场景 |
三者对比
| 特性 | select |
poll |
epoll |
|---|---|---|---|
| 文件描述符数量限制 | 有限,通常为1024 | 无硬性限制 | 无硬性限制 |
| 事件通知机制 | 遍历所有文件描述符 | 遍历所有文件描述符 | 只返回就绪的事件 |
| 时间复杂度 | O(n) | O(n) | O(1) |
| 内存拷贝 | 每次调用拷贝fd_set |
每次调用拷贝pollfd |
不需要拷贝所有文件描述符 |
| 触发模式 | LT | LT | LT和ET都支持 |
| 跨平台性 | 是 | 是 | 仅支持Linux |