跳到主要内容

node 基础

单线程

主线程
  • js 负责解释和执行代码的线程只有一个, 称为主线程;
工作线程
  • 浏览器或 node 存在其他线程, 处理 http, dom, IO 等, 统称为工作线程;
意义
  • 避免多线程同步的问题;
  • 降低应用程序内存占用;
  • 降低上下文切换开销;

非堵塞 IO

基本原理

  • 基于操作系统异步 IO 接口, 后台进行 IO 操作;
  • node 线程继续执行其他任务;
  • IO 操作完成后, node 通知应用程序, 进行对应操作;

优缺点

  • 提高 IO 并发处理能力, 提高服务器性能;

流程

  • node 调用操作系统 IO 接口;
  • 操作系统接受并挂起 IO 请求, 立刻返回给 node, node 继续执行其他任务;
  • 操作系统基于线程池异步处理 IO 操作;
  • IO 操作完成后, 操作系统向 node 发送信号, 并保存结果;
  • node 接受信号, 读取处理结果, 执行对应回调函数;

事件驱动

事件驱动

  • 基于事件监听, 事件循环和任务队列;
  • 某事件触发, node 执行对应事件处理程序;

事件循环

简要流程
  • node 启动, 创建事件循环和主线程;
  • node 将所有的 IO 请求, 定时器请求和异步函数放入任务队列中;
  • 主线程开始一次循环;
    • 首先执行同步函数;
    • 其次检查任务队列是否具有可执行的任务;
  • 若任务队列存在已完成任务, 依次取出并放置于主线程执行;
    • 若任务为 IO 请求或定时器;
      • node 调用系统 IO 异步接口或定时器接口;
      • 将该任务设置为挂起状态, 继续执行其他任务;
      • 系统 IO 异步操作或定时器超时, 操作系统通知 node 任务完成, node 将其加入任务队列;
    • 其余任务调用对应回调函数, 进入轮询等待阶段, 直至任务执行完成;
  • 若不存在, 进行下一次循环;

事件循环

6 个阶段
  • 6 个阶段;
    • timers 阶段;
      • 处理 setTimeout 和 setInterval;
      • 检查每一个计时器, 判断是否到达时间, 达到执行回调函数;
    • pending callback/IO callback;
      • 处理上一阶段少数未被执行的 IO 回调;
    • idle prepare: node 内部使用;
    • poll: 检索并执行 IO 相关的回调函数;
      • 如果其他队列出现回调, 进入 check 阶段;
      • 反之再该阶段一直等待;
    • check: 执行 setImmediate() 回调;
    • close callback: 执行一些 close() 的回调函数(socket, file);
  • 每个阶段具有自己的任务队列;
  • 每个阶段之间首先清理 process.nextTick() 对应回调, 再清理其他微任务回调;

宏任务和微任务

  • 与浏览器一致, 只是作用时机不同;
  • 浏览器在每一个宏任务执行前, 都要检查, 执行和保证微任务队列为空;
  • node 只在 6 个阶段之间检查, 执行和保证微任务队列为空;
  • 相关 API;
    • 微任务: process.nextTick();
    • 宏任务: setImmediate();

同步任务和异步任务

  • 同步任务: 主线程排队执行, 堵塞线程, 常以 sync 结尾;
  • 异步任务: 基于事件循环和任务队列, 不堵塞线程;
// 堵塞函数
copyBigFileSync("A.txt", "B.txt");
// 堵塞线程, 所有操作将在函数执行完成后才会执行
console.log("copy finished");

// 非阻塞式函数
console.log("start copying ... ");
copyBigFile("A.txt", "B.txt", () => {
// ...
});
// 未堵塞线程, 后续操作立刻执行
console.log("copy finished");