select,poll,epoll
1. 阻塞与非阻塞 (Blocking vs Non-Blocking)
**阻塞 (Blocking)**:
- 操作在完成之前会暂停调用者的执行。例如,在阻塞I/O中,调用
read()
或write()
时,线程会等待直到操作完成或超时。 - 特点:简单但效率较低,适合单线程或低并发场景。
- 示例:阻塞socket的
recv()
调用会等待数据到达。
- 操作在完成之前会暂停调用者的执行。例如,在阻塞I/O中,调用
**非阻塞 (Non-Blocking)**:
- 操作立即返回,无论是否完成。如果数据或资源不可用,通常返回错误码(如
EAGAIN
)或部分结果。 - 特点:适合高并发场景,但需要开发者处理未完成的情况。
- 示例:非阻塞socket的
recv()
会立即返回,若无数据则返回错误码。
- 操作立即返回,无论是否完成。如果数据或资源不可用,通常返回错误码(如
2. 异步与同步 (Asynchronous vs Synchronous)
**同步 (Synchronous)**:
- 调用者发起操作后,必须等待操作完成才能继续执行后续代码。
- 阻塞和非阻塞都可以是同步的。例如,阻塞I/O是同步的(等待完成),非阻塞I/O也是同步的(立即返回但需要轮询)。
- 特点:逻辑简单,但可能导致线程空闲或频繁检查。
**异步 (Asynchronous)**:
- 调用者发起操作后无需等待操作完成,系统通过回调、事件循环或Future/Promise通知调用者结果。
- 通常与非阻塞结合使用,适合高并发场景。
- 示例:异步I/O(如Linux的
io_uring
或JavaScript的async/await
)在操作完成时触发回调。
3. 水平触发与边缘触发 (Level-Triggered vs Edge-Triggered)
这些概念主要出现在事件驱动的I/O多路复用机制中(如select
、poll
、epoll
)。
**水平触发 (Level-Triggered)**:
- 事件触发条件基于状态(level)。只要文件描述符(fd)的状态满足条件(例如有数据可读),事件就会持续触发。
- 特点:需要处理所有就绪的数据,否则会反复触发。适合简单场景。
- 示例:在
select
或poll
中,如果socket有数据未读,每次检查都会报告可读。
**边缘触发 (Edge-Triggered)**:
- 事件触发条件基于状态变化(edge)。仅在文件描述符状态发生变化时触发一次(如从无数据到有数据)。
- 特点:效率高,但需要开发者一次性处理所有数据,否则可能丢失事件。常用于高性能场景。
- 示例:Linux的
epoll
在EPOLLET
模式下,仅在数据到达时触发一次。
总结与对比
特性 | 阻塞 | 非阻塞 | 同步 | 异步 | 水平触发 | 边缘触发 |
---|---|---|---|---|---|---|
等待行为 | 等待操作完成 | 立即返回 | 等待或轮询 | 不等待,回调通知 | 持续触发(状态满足) | 仅状态变化时触发 |
效率 | 低(线程阻塞) | 高(需轮询或管理) | 较低(需主动检查) | 高(事件驱动) | 较低(反复通知) | 高(单次通知) |
复杂度 | 简单 | 较复杂 | 简单 | 较复杂 | 简单 | 复杂 |
典型场景 | 简单程序 | 高并发服务器 | 传统I/O | 事件循环(如Node.js) | select /poll |
epoll /kqueue |
示例场景
假设你正在开发一个网络服务器:
- 阻塞 + 同步:每个客户端连接分配一个线程,线程调用阻塞
read()
等待数据。简单但线程开销大。 - 非阻塞 + 同步:单线程使用
select
或poll
轮询多个socket,检查哪些有数据。效率较高但需要手动管理。 - 非阻塞 + 异步:使用
epoll
(边缘触发)或io_uring
,数据到达时通过回调或事件循环处理,适合高并发。 - 水平触发 vs 边缘触发:在
epoll
中,水平触发下未读完的数据会反复通知;边缘触发下只通知一次,需确保读完所有数据。
select,poll的缺点
1.select采用位图进行文件描述符,能监视的连接数量有上限,一般是1024,poll采用链表存储,但都是通过轮询来访问监视连接,监视数量越大,性能越差
2.select和poll都需要拷贝大量句柄数据结构在用户态和内核态之间切换,产生巨大的开销
3.select和poll都是返回一个包含所有句柄的数组,应用程序仍需要遍历才能知道哪些句柄发生了事件
4.只支持水平触发,
epoll的优点
- 支持水平触发(默认)和边缘触发(
EPOLLET
模式)。 - 高效
- 内核使用红黑树管理 fd,添加/删除复杂度为 O(log n)。
- 就绪事件通过事件列表返回,仅包含就绪 fd,复杂度为 O(1)。
- 避免用户态和内核态频繁拷贝整个 fd 集合。
- 扩展性:支持大量 fd(仅受内存限制)。
- 边缘触发优势:仅在状态变化时通知,适合高性能服务器,但需确保处理所有数据。
All articles on this blog are licensed under CC BY-NC-SA 4.0 unless otherwise stated.
Comments