3.2 异步I/O实现现状

54 阅读2分钟

这一小节是全章最“硬核”的部分之一,朴灵作者详细对比了不同操作系统底层对异步I/O的实现方式。为什么需要这一节?因为Node要跨平台(Unix/Linux、Windows、macOS),必须抽象不同系统的异步机制,而libuv就是干这个的。

作者先指出:真正的异步I/O依赖操作系统内核支持,不同系统有不同API。Node通过libuv统一封装,让上层JS代码无感。

各种异步I/O实现机制对比

书里用一张经典表格对比了主流机制(很多读者都截了这页):

  1. select(最古老,BSD Unix引入)

    • 轮询方式:用户态传文件描述符集合给内核,内核遍历检查就绪。
    • 缺点:
      • 单次最多1024个fd(FD_SETSIZE限制)。
      • 每次调用需拷贝fd集合到内核,开销大。
      • 返回后用户态需遍历检查谁就绪(O(n))。
    • 现在基本淘汰,只作兼容。
  2. poll(System V Unix改进select)

    • 解决select的fd数量限制(无上限)。
    • 仍需拷贝fd数组,仍需O(n)遍历检查。
    • 缺点类似select。
  3. epoll(Linux 2.6内核引入,Node在Linux下主力)

    • 事件通知 + 就绪列表:用户态注册感兴趣事件,内核只通知就绪fd。
    • 优点:
      • 无fd数量限制。
      • 无需每次拷贝整个集合(只拷贝事件注册)。
      • 返回就绪列表,用户态直接处理(O(1))。
    • 两种模式:水平触发(LT,默认)和边缘触发(ET,更高效但复杂)。
  4. kqueue(FreeBSD/macOS引入,类似epoll)

    • 功能更强大:不仅网络I/O,还支持文件、进程、信号等事件。
    • 接口:kevent注册事件,内核返回就绪事件队列。
    • Node在macOS/BSD下用这个。
  5. IOCP(Windows的I/O Completion Port,完成端口)

    • Windows独有,设计最复杂但高效。
    • 机制:线程池 + 完成队列。
      • 发起异步I/O后,内核把完成通知放进队列。
      • 用户态线程从端口GetQueuedCompletionStatus取通知。
    • 优点:完美支持高并发,Windows服务器首选。
    • Node在Windows下用IOCP。

跨平台抽象:libuv的角色

不同系统API不统一,Node用libuv封装:

  • Unix系(Linux/macOS):优先epoll/kqueue,fallback到poll/select。
  • Windows:用IOCP。
  • libuv提供统一接口(如uv_poll、uv_fs等)。

这一小节读完,你就知道为什么Node在不同平台性能略有差异(Linux epoll最优,Windows IOCP也很强),也为下一节“Node的异步I/O”铺路——它是怎么利用libuv实现事件循环的。