您现在的位置是:网站首页 > 事件循环的阶段划分文章详情

事件循环的阶段划分

事件循环的阶段划分

Node.js 的事件循环是其异步 I/O 模型的核心,负责处理非阻塞操作。事件循环分为多个阶段,每个阶段执行特定类型的任务。理解这些阶段对于编写高效、可预测的 Node.js 应用至关重要。

定时器阶段(Timers)

定时器阶段处理 setTimeoutsetInterval 注册的回调函数。当定时器到期时,对应的回调会被放入事件队列,等待执行。

setTimeout(() => {
  console.log('Timeout 1');
}, 1000);

setInterval(() => {
  console.log('Interval 1');
}, 2000);

需要注意的是,定时器的执行时间并不精确。如果其他操作阻塞了事件循环,定时器可能会延迟执行。

待定回调阶段(Pending Callbacks)

这个阶段执行一些系统操作的回调,比如 TCP 错误。例如,当 TCP 套接字收到 ECONNREFUSED 时,相关的错误回调会在这个阶段执行。

const net = require('net');
const client = net.createConnection({ port: 8080 }, () => {
  console.log('Connected to server!');
});

client.on('error', (err) => {
  // 这个错误回调会在待定回调阶段执行
  console.error('Connection error:', err);
});

闲置、准备阶段(Idle, Prepare)

这是事件循环的内部阶段,通常不需要开发者直接干预。Node.js 在这个阶段进行一些内部准备工作。

轮询阶段(Poll)

轮询阶段是事件循环的核心阶段,它有两个主要功能:

  1. 计算应该阻塞和轮询 I/O 的时间
  2. 处理轮询队列中的事件
const fs = require('fs');

fs.readFile('/path/to/file', 'utf8', (err, data) => {
  if (err) throw err;
  console.log(data);
});

// 其他同步代码
console.log('Reading file...');

在这个例子中,文件读取操作的回调会在轮询阶段执行,而同步的 console.log 会立即执行。

检查阶段(Check)

setImmediate 注册的回调在这个阶段执行。它总是在轮询阶段之后立即执行。

setImmediate(() => {
  console.log('Immediate 1');
});

setTimeout(() => {
  console.log('Timeout 2');
}, 0);

有趣的是,虽然 setTimeout 的延迟为 0,但 setImmediate 的回调通常会先执行,因为它在检查阶段,而 setTimeout 在定时器阶段。

关闭回调阶段(Close Callbacks)

这个阶段处理一些关闭事件的回调,比如 socket.on('close', ...)

const server = require('net').createServer();

server.on('connection', (socket) => {
  socket.on('close', () => {
    console.log('Socket closed');
  });
});

server.listen(8080);

当套接字关闭时,相关的回调会在这个阶段执行。

阶段执行顺序

事件循环的阶段按照以下顺序执行:

  1. 定时器
  2. 待定回调
  3. 闲置、准备
  4. 轮询
  5. 检查
  6. 关闭回调

这个过程会不断重复,直到没有更多的工作需要处理。

微任务队列

除了这些主要阶段,Node.js 还有微任务队列(Microtask Queue),包括:

  • process.nextTick
  • Promise 回调

这些微任务会在当前阶段结束后立即执行,优先于下一个阶段。

Promise.resolve().then(() => {
  console.log('Promise resolved');
});

process.nextTick(() => {
  console.log('Next tick');
});

setImmediate(() => {
  console.log('Immediate');
});

执行顺序会是:nextTick → Promise → Immediate。

阶段转换示例

const fs = require('fs');

// 阶段1:定时器
setTimeout(() => {
  console.log('Timeout');
}, 0);

// 阶段4:轮询
fs.readFile('/path/to/file', () => {
  // 阶段5:检查
  setImmediate(() => {
    console.log('Immediate inside readFile');
    
    // 微任务
    process.nextTick(() => {
      console.log('NextTick inside Immediate');
    });
  });
  
  // 微任务
  Promise.resolve().then(() => {
    console.log('Promise inside readFile');
  });
});

// 微任务
process.nextTick(() => {
  console.log('NextTick');
});

这个例子展示了不同阶段和微任务的执行顺序。

阶段与性能优化

理解事件循环阶段有助于性能优化。例如,CPU 密集型任务应该分解为小块,使用 setImmediateprocess.nextTick 让事件循环继续处理其他任务。

function processChunk(data, chunkSize, callback) {
  let index = 0;
  
  function next() {
    const chunk = data.slice(index, index + chunkSize);
    // 处理数据块...
    
    index += chunkSize;
    if (index < data.length) {
      // 使用 setImmediate 让事件循环继续
      setImmediate(next);
    } else {
      callback();
    }
  }
  
  next();
}

阶段与错误处理

不同阶段的错误处理方式可能不同。例如,Promise 拒绝应该在微任务阶段处理,而普通的回调错误可能在轮询阶段处理。

// 微任务错误
Promise.reject(new Error('Promise error')).catch(err => {
  console.error('Caught promise error:', err);
});

// 普通回调错误
fs.readFile('nonexistent.txt', (err) => {
  if (err) {
    console.error('Caught fs error:', err);
  }
});

浏览器与Node.js事件循环差异

浏览器的事件循环模型与Node.js有所不同。例如,浏览器没有setImmediate,但有requestAnimationFrame等特定于渲染的API。

// 浏览器中的代码
requestAnimationFrame(() => {
  console.log('Animation frame');
});

// Node.js中没有requestAnimationFrame

阶段监控与调试

可以使用perf_hooks模块监控事件循环阶段:

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

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

setInterval(() => {
  console.log(`Event loop delay: ${histogram.mean} ms`);
  histogram.reset();
}, 1000);

这个工具可以帮助识别事件循环中的延迟问题。

阶段与集群

在集群模式下,每个工作进程都有自己的事件循环。理解这一点对于设计可扩展的Node.js应用很重要。

const cluster = require('cluster');
const http = require('http');

if (cluster.isMaster) {
  // 主进程
  for (let i = 0; i < 4; i++) {
    cluster.fork();
  }
} else {
  // 工作进程
  http.createServer((req, res) => {
    res.end('Hello from worker');
  }).listen(8080);
}

每个工作进程独立处理请求,有自己独立的事件循环。

阶段与子进程

子进程也运行在独立的事件循环中。父进程和子进程通过IPC通信。

const { fork } = require('child_process');

const child = fork('child.js');

child.on('message', (msg) => {
  console.log('Message from child:', msg);
});

child.send({ hello: 'world' });

阶段与Worker Threads

Worker Threads提供了真正的多线程能力,每个线程有独立的事件循环。

const { Worker } = require('worker_threads');

const worker = new Worker(`
  const { parentPort } = require('worker_threads');
  parentPort.on('message', (msg) => {
    console.log('Worker received:', msg);
    parentPort.postMessage('Hello from worker');
  });
`, { eval: true });

worker.on('message', (msg) => {
  console.log('Main thread received:', msg);
});

worker.postMessage('Hello from main thread');

阶段与异步迭代

Node.js的异步迭代器也依赖于事件循环阶段。

async function processStream(stream) {
  for await (const chunk of stream) {
    console.log('Processing chunk:', chunk);
    // 使用setImmediate让事件循环继续
    await new Promise(resolve => setImmediate(resolve));
  }
}

阶段与AbortController

现代Node.js版本支持AbortController,可以在不同阶段中断操作。

const controller = new AbortController();
const { signal } = controller;

setTimeout(() => {
  controller.abort();
}, 1000);

fetch('https://example.com', { signal })
  .then(response => response.text())
  .then(text => console.log(text))
  .catch(err => {
    if (err.name === 'AbortError') {
      console.log('Request aborted');
    }
  });

阶段与性能测量

可以使用perf_hooks测量不同阶段的性能:

const { performance, PerformanceObserver } = require('perf_hooks');

const obs = new PerformanceObserver((items) => {
  console.log(items.getEntries()[0]);
  performance.clearMarks();
});
obs.observe({ entryTypes: ['measure'] });

performance.mark('A');
setImmediate(() => {
  performance.mark('B');
  performance.measure('A to B', 'A', 'B');
});

阶段与资源限制

Node.js允许设置事件循环延迟的监控和资源限制:

const { ResourceUsage } = require('process');

setInterval(() => {
  const usage = process.resourceUsage();
  console.log('User CPU time:', usage.ru_utime);
  console.log('System CPU time:', usage.ru_stime);
}, 1000);

阶段与诊断报告

Node.js的诊断报告可以帮助分析事件循环问题:

process.on('SIGUSR2', () => {
  process.report.writeReport();
});

console.log(`Run 'kill -USR2 ${process.pid}' to generate report`);

阶段与Async Hooks

Async Hooks API可以跟踪异步资源的生命周期:

const async_hooks = require('async_hooks');

const hook = async_hooks.createHook({
  init(asyncId, type, triggerAsyncId) {
    console.log(`Init: ${type} (${asyncId})`);
  },
  destroy(asyncId) {
    console.log(`Destroy: ${asyncId}`);
  }
});

hook.enable();

setTimeout(() => {
  console.log('Timeout executed');
}, 100);

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步