1. 阻塞与非阻塞 (Blocking vs Non-Blocking)

  • **阻塞 (Blocking)**:

    • 操作在完成之前会暂停调用者的执行。例如,在阻塞I/O中,调用read()write()时,线程会等待直到操作完成或超时。
    • 特点:简单但效率较低,适合单线程或低并发场景。
    • 示例:阻塞socket的recv()调用会等待数据到达。
  • **非阻塞 (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多路复用机制中(如selectpollepoll)。

  • **水平触发 (Level-Triggered)**:

    • 事件触发条件基于状态(level)。只要文件描述符(fd)的状态满足条件(例如有数据可读),事件就会持续触发。
    • 特点:需要处理所有就绪的数据,否则会反复触发。适合简单场景。
    • 示例:在selectpoll中,如果socket有数据未读,每次检查都会报告可读。
  • **边缘触发 (Edge-Triggered)**:

    • 事件触发条件基于状态变化(edge)。仅在文件描述符状态发生变化时触发一次(如从无数据到有数据)。
    • 特点:效率高,但需要开发者一次性处理所有数据,否则可能丢失事件。常用于高性能场景。
    • 示例:Linux的epollEPOLLET模式下,仅在数据到达时触发一次。

总结与对比

特性 阻塞 非阻塞 同步 异步 水平触发 边缘触发
等待行为 等待操作完成 立即返回 等待或轮询 不等待,回调通知 持续触发(状态满足) 仅状态变化时触发
效率 低(线程阻塞) 高(需轮询或管理) 较低(需主动检查) 高(事件驱动) 较低(反复通知) 高(单次通知)
复杂度 简单 较复杂 简单 较复杂 简单 复杂
典型场景 简单程序 高并发服务器 传统I/O 事件循环(如Node.js) select/poll epoll/kqueue

示例场景

假设你正在开发一个网络服务器:

  • 阻塞 + 同步:每个客户端连接分配一个线程,线程调用阻塞read()等待数据。简单但线程开销大。
  • 非阻塞 + 同步:单线程使用selectpoll轮询多个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(仅受内存限制)。
  • 边缘触发优势:仅在状态变化时通知,适合高性能服务器,但需确保处理所有数据。