这一小节是全章最“硬核”的部分之一,朴灵作者详细对比了不同操作系统底层对异步I/O的实现方式。为什么需要这一节?因为Node要跨平台(Unix/Linux、Windows、macOS),必须抽象不同系统的异步机制,而libuv就是干这个的。
作者先指出:真正的异步I/O依赖操作系统内核支持,不同系统有不同API。Node通过libuv统一封装,让上层JS代码无感。
各种异步I/O实现机制对比
书里用一张经典表格对比了主流机制(很多读者都截了这页):
-
select(最古老,BSD Unix引入)
- 轮询方式:用户态传文件描述符集合给内核,内核遍历检查就绪。
- 缺点:
- 单次最多1024个fd(FD_SETSIZE限制)。
- 每次调用需拷贝fd集合到内核,开销大。
- 返回后用户态需遍历检查谁就绪(O(n))。
- 现在基本淘汰,只作兼容。
-
poll(System V Unix改进select)
- 解决select的fd数量限制(无上限)。
- 仍需拷贝fd数组,仍需O(n)遍历检查。
- 缺点类似select。
-
epoll(Linux 2.6内核引入,Node在Linux下主力)
- 事件通知 + 就绪列表:用户态注册感兴趣事件,内核只通知就绪fd。
- 优点:
- 无fd数量限制。
- 无需每次拷贝整个集合(只拷贝事件注册)。
- 返回就绪列表,用户态直接处理(O(1))。
- 两种模式:水平触发(LT,默认)和边缘触发(ET,更高效但复杂)。
-
kqueue(FreeBSD/macOS引入,类似epoll)
- 功能更强大:不仅网络I/O,还支持文件、进程、信号等事件。
- 接口:kevent注册事件,内核返回就绪事件队列。
- Node在macOS/BSD下用这个。
-
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实现事件循环的。