阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 单线程与事件循环

单线程与事件循环

作者:陈川 阅读数:47307人阅读 分类: Node.js

单线程的本质

Node.js 采用单线程模型处理 JavaScript 代码执行。这个设计意味着主线程在同一时刻只能执行一个任务。与多线程环境不同,它不需要处理线程同步、锁竞争等复杂问题。单线程的核心优势在于简化了并发模型,但也带来了潜在的性能瓶颈。

function computeIntensiveTask() {
  let sum = 0;
  for (let i = 0; i < 1e9; i++) {
    sum += i;
  }
  return sum;
}

console.log('Start');
computeIntensiveTask(); // 阻塞主线程
console.log('End'); // 长时间延迟后才会执行

事件循环机制

事件循环是 Node.js 实现非阻塞 I/O 的关键。它本质上是一个无限循环,不断检查事件队列并执行回调。整个机制分为多个阶段,每个阶段处理特定类型的任务:

  1. 定时器阶段:执行 setTimeout 和 setInterval 回调
  2. I/O 回调阶段:处理网络、文件等 I/O 事件
  3. 闲置/准备阶段:内部使用
  4. 轮询阶段:检索新的 I/O 事件
  5. 检查阶段:执行 setImmediate 回调
  6. 关闭回调阶段:处理如 socket.on('close') 等事件
setTimeout(() => console.log('Timeout'), 0);
setImmediate(() => console.log('Immediate'));

// 输出顺序可能不同,取决于事件循环的启动时间

非阻塞 I/O 实现

Node.js 通过 libuv 库实现真正的异步 I/O。当遇到 I/O 操作时,主线程会将操作委托给系统内核,然后继续执行后续代码。内核完成操作后,将结果放入事件队列,等待事件循环处理。

const fs = require('fs');

console.log('开始读取文件');
fs.readFile('largefile.txt', (err, data) => {
  console.log('文件读取完成');
});
console.log('继续执行其他任务');

// 输出顺序:
// 开始读取文件
// 继续执行其他任务
// 文件读取完成

微任务与宏任务

事件循环处理的任务分为两种优先级:

  • 微任务:process.nextTick、Promise 回调
  • 宏任务:setTimeout、setInterval、I/O 操作

微任务在当前阶段结束后立即执行,优先级高于宏任务。

Promise.resolve().then(() => console.log('Promise'));
process.nextTick(() => console.log('nextTick'));
setTimeout(() => console.log('Timeout'), 0);

// 输出顺序:
// nextTick
// Promise
// Timeout

常见的性能陷阱

单线程模型下,CPU 密集型任务会阻塞事件循环:

// 错误示例:同步加密大文件
const crypto = require('crypto');
function encryptLargeFile() {
  const data = Buffer.alloc(1e8); // 100MB 数据
  return crypto.createHash('sha256').update(data).digest('hex');
}

// 正确做法:使用流式处理或工作线程
const { Worker } = require('worker_threads');
function asyncEncrypt(filePath) {
  return new Promise((resolve) => {
    const worker = new Worker('./encrypt-worker.js', { workerData: filePath });
    worker.on('message', resolve);
  });
}

事件循环的监控

Node.js 提供了性能监控接口,可以检测事件循环延迟:

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

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

setInterval(() => {
  console.log(`事件循环延迟(ms): 
    p50=${histogram.percentile(50)/1e6}, 
    p99=${histogram.percentile(99)/1e6}`);
  histogram.reset();
}, 1000);

实际应用优化

Web 服务器中的最佳实践:

const express = require('express');
const app = express();

// 错误处理中间件应该声明在最前面
app.use((err, req, res, next) => {
  console.error(err);
  res.status(500).send('Server Error');
});

// CPU 密集型路由应该使用工作线程
app.get('/compute', async (req, res) => {
  const result = await runInWorker('./compute.js');
  res.json({ result });
});

// I/O 密集型路由可以直接处理
app.get('/data', (req, res) => {
  db.query('SELECT * FROM large_table', (err, data) => {
    if (err) return next(err);
    res.json(data);
  });
});

高级模式应用

利用事件循环特性实现流量控制:

class RateLimiter {
  constructor(limit) {
    this.queue = [];
    this.active = 0;
    this.limit = limit;
  }

  async execute(task) {
    if (this.active >= this.limit) {
      await new Promise(resolve => this.queue.push(resolve));
    }

    this.active++;
    try {
      return await task();
    } finally {
      this.active--;
      if (this.queue.length) {
        this.queue.shift()();
      }
    }
  }
}

// 使用示例
const limiter = new RateLimiter(5);
for (let i = 0; i < 100; i++) {
  limiter.execute(() => fetch('https://api.example.com/data/' + i));
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

上一篇:非阻塞I/O模型

下一篇:CommonJS模块系统

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌