全部学科
Python全栈
python
NodeJS全栈
nodejs
小程序首页
📅 2026-05-15 9 分钟 ✍️ juanwangdev

Node.js 事件循环与线程池

事件循环和线程池是 Node.js 高效 I/O 的核心。

事件循环

六个阶段

JavaScript
// 事件循环阶段顺序
   ┌───────────────────────────┐
┌─>           timers           setTimeout/setInterval
  └─────────────────┬─────────┘
  ┌─────────────────┴─────────┐
       pending callbacks      I/O 回调
  └─────────────────┬─────────┘
  ┌─────────────────┴─────────┐
         idle, prepare        内部使用
  └─────────────────┬─────────┘
  ┌─────────────────┴─────────┐
             poll             I/O 事件
  └─────────────────┬─────────┘
  ┌─────────────────┴─────────┐
             check            setImmediate
  └─────────────────┬─────────┘
  ┌─────────────────┴─────────┐
└──│      close callbacks       close 事件
   └───────────────────────────┘

各阶段说明

阶段说明示例
timers执行定时器回调setTimeout, setInterval
pending callbacks执行延迟 I/O 回调系统操作回调
idle, prepare内部使用libuv 内部
poll执行 I/O 回调,等待新事件fs, net 回调
check执行 setImmediate 回调setImmediate
close callbacks执行 close 回调socket.destroy

定时器执行顺序

setTimeout vs setImmediate

JavaScript
// 在 I/O 回调中
fs.readFile('file.txt', () => {
  setTimeout(() => console.log('timeout'), 0);
  setImmediate(() => console.log('immediate'));
});

// 输出顺序:immediate -> timeout
// 因为在 poll 阶段后先执行 check

在主模块中

JavaScript
// 非 I/O 回调中顺序不确定
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
// 顺序取决于进程启动性能

process.nextTick

微任务队列

JavaScript
// nextTick 在事件循环每个阶段后执行
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
process.nextTick(() => console.log('nextTick'));

// 输出:nextTick -> timeout/immediate

Promise 微任务

JavaScript
// Promise.then 在 nextTick 之后
process.nextTick(() => console.log('tick'));
Promise.resolve().then(() => console.log('promise'));
console.log('sync');

// 输出:sync -> tick -> promise

线程池

libuv 线程池

JavaScript
// 默认 4 个线程
// 处理以下操作:
// - 文件系统操作
// - DNS 解析
// - 用户自定义任务

// 调整线程数
process.env.UV_THREADPOOL_SIZE = 8;

线程池处理的操作

操作是否使用线程池
fs.readFile
fs.writeFile
dns.lookup
crypto.pbkdf2
zlib 压缩
net.Socket否(系统异步)
http.request否(系统异步)

事件循环阻塞

CPU 密集型阻塞

JavaScript
// 阻塞事件循环
function heavyCompute() {
  for (let i = 0; i < 1e9; i++) {
    // 计算阻塞主线程
  }
}

heavyCompute(); // 阻塞所有异步操作

解决方案

JavaScript
// 分片执行
function computeChunked(total, chunkSize, callback) {
  let count = 0;
  const chunk = () => {
    for (let i = 0; i < chunkSize && count < total; i++) {
      // 处理一块
      count++;
    }
    if (count < total) {
      setImmediate(chunk); // 让出事件循环
    } else {
      callback();
    }
  };
  chunk();
}

// 或使用 Worker
const { Worker } = require('worker_threads');

监控事件循环延迟

使用 perf_hooks

JavaScript
const { monitorEventLoopDelay } = require('perf_hooks');

const h = monitorEventLoopDelay();
h.enable();

// 定期检查
setInterval(() => {
  console.log('延迟:', h.mean, 'ms');
  console.log('最大延迟:', h.max, 'ms');
}, 1000);

h.disable();

手动检测

JavaScript
let lastTime = Date.now();

setInterval(() => {
  const now = Date.now();
  const delay = now - lastTime - 100; // 预期间隔100ms
  if (delay > 50) {
    console.log('事件循环延迟:', delay, 'ms');
  }
  lastTime = now;
}, 100);

队列溢出

nextTick 队列溢出

JavaScript
// 递归调用 nextTick 阻塞
function recursiveNextTick() {
  process.nextTick(() => {
    recursiveNextTick(); // 永不执行 I/O
  });
}

setImmediate 替代

JavaScript
// setImmediate 让事件循环继续
function recursiveImmediate() {
  setImmediate(() => {
    recursiveImmediate(); // 允许 I/O 执行
  });
}

注意事项

  • 事件循环单线程,CPU 密集型会阻塞
  • 定时器不精确,受事件循环影响
  • nextTick 优先于 Promise
  • I/O 回调中 setImmediate 优于 setTimeout
  • 线程池默认 4 线程,可调整

要点总结

  • 事件循环六阶段:timers → pending → poll → check → close
  • process.nextTick 最高优先级
  • setImmediate 在 I/O 回调中先于 setTimeout(0)
  • libuv 线程池处理文件、DNS、加密
  • CPU 密集型使用 Worker 或分片执行

📝 发现内容有误?点击此处直接编辑

← 上一篇 Node.js worker_threads模块
下一篇 → Node.js 进程与线程概念
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库