当前位置: 首页 > news >正文

【Node.js从 0 到 1:入门实战与项目驱动】1.2 Node.js 的核心优势(非阻塞 I/O、事件驱动、单线程模型)

文章目录

  • 1.2 Node.js 的核心优势(非阻塞 I/O、事件驱动、单线程模型)
        • 1.2.1 单线程模型:“一个服务员” 高效管全场
        • 1.2.2 非阻塞 I/O:“等菜时不闲着” 的高效协作
          • 场景 1:**`阻塞 I/O`**(`fs.readFileSync`)
          • 场景 2:**`非阻塞 I/O`**(`fs.readFile`)
        • 1.2.3 事件驱动:“按号上菜” 的有序调度
        • 1.2.4 三大优势如何协同?高并发场景的 “降维打击”

1.2 Node.js 的核心优势(非阻塞 I/O、事件驱动、单线程模型)

Node.js 能在高并发场景(如实时聊天、API 服务器)中表现出色,核心依赖于三大特性的协同工作。

  • 我们用「餐厅运营」的场景类比,再结合代码实战,彻底搞懂这些优势。
1.2.1 单线程模型:“一个服务员” 高效管全场
  • 概念

    • Node.js 采用「单线程」执行 JavaScript 代码 —— 整个程序只有一个主线程处理所有任务,不像传统服务器(如 Java)启动多个线程并行处理。
  • 生活类比

    • 传统多线程服务器像「多个服务员各自服务一桌客人」,虽然能同时处理多单,但招聘服务员(创建线程)成本高,服务员之间还可能抢资源(线程冲突);
  • Node.js 单线程像「一个超级服务员管全场」,通过高效调度(事件循环),同时应对多桌客人的点餐、催单、结账,人力成本极低

  • 代码验证:单线程的 “顺序执行” 与 “阻塞性”

    • 单线程意味着代码按顺序执行,前一个任务没完成,后一个任务必须等待(同步阻塞)。

      // 单线程执行顺序演示console.log("客人A:点一份牛排");// 模拟一个耗时任务(如切牛排,耗时2秒)const start = Date.now();while (Date.now() - start < 2000) {} // 阻塞主线程2秒console.log("客人A:牛排好了");console.log("客人B:点一杯咖啡"); // 必须等客人A的任务完成才执行
      

      运行结果(终端输入 node singleThread.js):
      在这里插入图片描述

  • 关键结论

      • 单线程的 “阻塞性” 是把双刃剑:简单场景下避免线程切换开销,但如果有耗时任务(如复杂计算),会卡住整个程序。
      • 但 Node.js 通过「非阻塞 I/O」和「事件驱动」解决了这个问题 —— 让 “服务员” 在等牛排煎熟时,去处理其他客人的需求。
1.2.2 非阻塞 I/O:“等菜时不闲着” 的高效协作
  • 概念

    • I/O 操作(如读写文件、数据库查询、网络请求)是程序中最耗时的环节(比如读一个大文件可能要 1 秒)。
      • 「阻塞 I/O」:等 I/O 完成后才继续执行(服务员站在厨房门口等菜,期间啥也不做);
      • 「非阻塞 I/O」:发起 I/O 后不等待,直接处理其他任务,I/O 完成后通过回调通知(服务员把菜单交给厨房后,去招呼其他客人,菜做好了再回来上菜)。
  • 代码实战:阻塞 vs 非阻塞读取文件

我们用读取两个大文件的场景对比,直观感受效率差异。

场景 1:阻塞 I/Ofs.readFileSync
const fs = require('fs');
const path = require('path');
const file1 = path.join('./largeFile1.txt');
const file2 = path.join('./largeFile2.txt');
console.log("开始读取文件1(阻塞方式)");const start1 = Date.now();
const data1 = fs.readFileSync(file1); // 阻塞主线程,直到读完
console.log(`文件1读取完成,耗时${Date.now() - start1}ms`);
console.log("开始读取文件2(阻塞方式)");
const start2 = Date.now();
const data2 = fs.readFileSync(file2); // 必须等文件1读完才开始
console.log(`文件2读取完成,耗时${Date.now() - start2}ms`);
console.log(`总耗时:${Date.now() - start1}ms`);
  • 运行结果(假设每个文件读取需 1 秒):
    在这里插入图片描述
场景 2:非阻塞 I/Ofs.readFile
  • const fs = require('fs');const path = require('path');
    const file1 = path.join('./largeFile1.txt');
    const file2 = path.join('./largeFile2.txt');console.log("开始读取文件1(非阻塞方式)");const start = Date.now();fs.readFile(file1, (err, data1) => { // 发起读取后立即返回console.log(`文件1读取完成,耗时${Date.now() - start}ms`);
    });console.log("开始读取文件2(非阻塞方式)");fs.readFile(file2, (err, data2) => { // 不用等文件1,立即发起console.log(`文件2读取完成,耗时${Date.now() - start}ms`);
    });console.log("发起读取后,我先去处理其他事..."); // 这行代码会先执行
    
  • 在这里插入图片描述

  • 关键结论

      • 非阻塞 I/O 让 Node.js 在等待 I/O 时不闲置,总耗时接近单个任务的耗时(而非阻塞方式的总和)。
      • 这也是 Node.js 适合「I/O 密集型场景」(如 API 服务器、文件处理)的核心原因 —— 大部分时间在等数据(数据库返回、文件读取),而非复杂计算
1.2.3 事件驱动:“按号上菜” 的有序调度
  • 概念

    • Node.js 用「事件循环(Event Loop)」机制调度所有任务,所有 I/O 操作、用户交互等都会被包装成「事件」,按顺序放入事件队列,主线程处理完当前任务后,不断从队列中取事件执行(类似餐厅按订单号上菜,不会乱序)。
  • 事件循环工作流程(通俗版)

      1. 主线程先执行同步代码(如 console.log);
      1. 遇到异步任务(如 setTimeoutfs.readFile),交给底层线程池处理(不用主线程等);
      1. 异步任务完成后,其回调函数被放入「事件队列」;
      1. 主线程空闲时,从事件队列中按顺序取回调执行(循环往复)。
  • 代码实战:可视化事件循环顺序

    // 同步代码:先执行console.log("1. 同步任务:开始做饭");// 异步任务1:延迟100ms
    setTimeout(() => {console.log("4. 异步任务:汤煮好了");
    }, 100);// 异步任务2:I/O操作(读取一个小文件)const fs = require('fs');fs.readFile('./largeFile1.txt', (err, data) => {console.log("3. 异步任务:菜炒好了");
    });// 同步代码:继续执行
    console.log("2. 同步任务:准备餐具");
    

运行逻辑拆解

    1. 先执行同步代码:打印 12
    1. setTimeoutfs.readFile 被交给底层处理,主线程继续执行同步代码;
    1. fs.readFile 完成快(小文件),其回调先入队,打印 3
    1. setTimeout 延迟到了,其回调入队,打印 4
  • 在这里插入图片描述

生活类比:事件驱动像医院叫号系统

    • 同步代码 = 当场挂号的病人,优先处理;
    • 异步任务 = 预约挂号的病人,到号了护士再叫(放入事件队列);
    • 事件循环 = 护士不断喊号,确保每个病人按顺序被处理,不插队。
1.2.4 三大优势如何协同?高并发场景的 “降维打击”

在高并发场景(如 1 秒内有 1000 个用户请求):

  • 单线程:避免多线程切换的资源消耗(不用频繁创建 / 销毁线程);
  • 非阻塞 I/O1000 个请求的 I/O 操作(查数据库、读文件)可以并行处理,主线程不卡
  • 事件驱动:通过事件队列有序调度所有请求的回调,确保每个请求被公平处理。

这就是**为什么 Node.js 能轻松应对「高并发 I/O 场景」(如直播弹幕、电商秒杀 API)**,而传统多线程服务器在同样场景下会因线程过多导致内存爆炸。

总结
Node.js 的三大优势不是孤立的

  • 单线程是基础(低成本),非阻塞 I/O 是手段(不浪费时间),事件驱动是调度核心(有序处理)
  • 三者结合,让它 在特定场景下实现了 “用更少资源做更多事” 的高效能

(下一节我们会具体讲这些优势在实际开发中的应用场景,比如为什么实时聊天应用首选 Node.js~)

http://www.dtcms.com/a/315300.html

相关文章:

  • 比起登天,孙宇晨更需要安稳着陆
  • 飞算 JavaAI:为软件安全上锁的智能守护者
  • Antlr学习笔记 02、使用antlr4实现简易版计算器
  • 【Z字形变换】
  • 离线Docker项目移植全攻略
  • 明远智睿T113-i核心板:工业设备制造领域成本控制的破局者
  • NX982NX984美光固态闪存NX992NY102
  • UVa1480/LA5034 Jewel
  • webm 读取解析
  • Linux 系统重置用户密码指南
  • go安装gin
  • JMeter 性能测试工具使用教程
  • 【网络运维】Linux:逻辑卷管理
  • Spring @Component注解全解析
  • 计算机网络:子网掩码在路由转发中的关键作用
  • css的选择器
  • 虚拟机中查看和修改文件权限
  • MoonBit Pearls Vol.04:用MoonBit 探索协同式编程
  • Spring Security 认证与授权实现机制
  • 随机森林知识点整理:从原理到实战
  • 课题学习4——将原系统的BERT换为SBERT
  • 【网络运维】Linux:RAID存储技术
  • 单类别目标检测中的 Varifocal Loss 与 mAP 评估:从原理到实践(特别前景和背景类区分)
  • Transformer核心机制:QKV全面解析
  • 图片处理工具类:基于 Thumbnailator 的便捷解决方案
  • Unsloth 大语言模型微调工具介绍
  • 数据结构:反转链表(reverse the linked list)
  • 机器视觉的产品包装帖纸模切应用
  • 深度学习-卷积神经网络CNN-卷积层
  • JMeter的基本使用教程