poll 阶段在无待处理定时器、i/o 回调队列为空且存在活跃 i/o 观察者时,会通过 libuv 调用操作系统阻塞式 i/o 多路复用(如 epoll_wait)主动挂起线程等待事件。

Node.js 的 Event Loop 中,Poll 阶段在没有待处理的定时器(如 setTimeout、setInterval)且任务队列为空时,会**主动阻塞等待 I/O 事件**(比如网络请求、文件读写完成、socket 数据到达等),而不是忙轮询或立即退出。
Poll 阶段如何“阻塞等待”
这背后不是 JavaScript 层面的循环等待,而是依赖底层 libuv 库调用操作系统的 I/O 多路复用机制:
- Linux 上使用
epoll_wait() - macOS 上使用
kqueue() - Windows 上使用
IOCP(完成端口)
这些系统调用本身是**阻塞的**:当没有就绪的 I/O 事件时,线程会挂起,交出 CPU,直到有 socket 可读、连接建立、DNS 解析完成等事件发生,内核才唤醒线程并返回就绪事件列表。
什么情况下 Poll 阶段会进入阻塞
满足以下全部条件时,Poll 阶段才会真正阻塞:
- 当前没有待执行的定时器(即 timer queue 为空,或最近到期的定时器还远未到时间)
- Poll 队列(I/O callback queue)当前为空
- 没有被 setImmediate() 或 nextTick() 推入的微任务正在排队(它们不影响 Poll 阻塞,但会影响后续阶段)
- 存在至少一个活跃的 I/O 观察者(例如:监听中的 TCP server、处于连接状态的 socket、打开的文件句柄等)
只要有一个活跃的 I/O 句柄(哪怕只是 net.createServer().listen(3000) 启动了一个空服务),libuv 就认为“可能有事发生”,就会在 Poll 阶段调用 epoll_wait() 等待,而不是直接跳过。
一个典型例子:HTTP 服务器空闲时的行为
运行下面这段代码:
const http = require('http');
http.createServer((req, res) => res.end('ok')).listen(3000);
此时 Node.js 进程:
- Timer 阶段:无定时器 → 跳过
- IO Callbacks / Idle / Prepare / Check / Close:均无任务 → 快速跳过
- Poll 阶段:发现有一个监听 socket(fd 已注册到 epoll),但暂无新连接或数据 → 调用
epoll_wait(-1)(-1 表示无限等待)→ 线程休眠
一旦有客户端发起 TCP 握手或发送 HTTP 请求,内核通知 epoll,Node.js 被唤醒,把连接/数据事件加入 Poll 队列,接着在 Poll 阶段执行对应的回调(如 connection listener、request handler)。
注意:阻塞 ≠ 卡死,也不影响新定时器插入
即使 Poll 阶段正在 epoll_wait() 中阻塞,你随时可以调用 setTimeout(cb, 100) —— libuv 会立刻中断等待(通过内部 pipe 或 timerfd 机制唤醒),转去检查定时器是否到期。所以定时器精度不会因 Poll 阻塞而丢失。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











