【Node】单线程的Node.js为什么可以实现多线程?

前言

很多刚接触 Node.js 的开发者都会有一个疑问:既然 Node.js 是单线程的,为什么又能使用 Worker Threads 这样的多线程模块呢?

今天我们就来解开这个看似矛盾的技术谜题。

👀 脑海里先有个印象:【Node.js 主线程】是单线程的,但【可以通过其他方式】实现并行处理

什么是 Node.js 的"单线程"?

事件循环(Event Loop)机制

复制代码

javascript

体验AI代码助手

代码解读

复制代码

// 这是一个简单的 Node.js 程序 console.log('开始执行') setTimeout(() => { console.log('定时器回调') }, 1000) console.log('继续执行') // 输出顺序: // 开始执行 // 继续执行 // 定时器回调

核心特点

  • Node.js 有一个主线程负责执行 JavaScript 代码
  • 这个主线程运行着事件循环,按顺序处理任务
  • I/O 操作(文件读写、网络请求等)被【委托给系统底层】,不阻塞主线程

单线程的优势

复制代码

javascript

体验AI代码助手

代码解读

复制代码

// 单线程模型简单易懂 let count = 0 function increment() { count++ console.log(count) } increment() // 输出 1 increment() // 输出 2 // 不用担心多线程的竞争条件问题

优点

  • ✅ 编程模型简单
  • ✅ 避免复杂的线程同步问题
  • ✅ 上下文切换开销小

那为什么还需要多线程?

单线程的局限性

复制代码

javascript

体验AI代码助手

代码解读

复制代码

// CPU 密集型任务会阻塞事件循环 function heavyCalculation() { let result = 0 for (let i = 0; i < 1000000000; i++) { result += Math.sqrt(i) * Math.sin(i) } return result } console.log('任务开始') heavyCalculation() // 在这期间,其他任务都无法执行! console.log('任务结束,但用户界面会卡住')

问题暴露

  • 一个复杂的计算任务会阻塞整个应用程序
  • 无法充分利用多核 CPU 的性能
  • 对于计算密集型应用性能受限

解开谜题:Node.js 的多线程能力

底层真相:Node.js 不是完全单线程!!!

实际上,Node.js 的架构是下面这样的:

复制代码

css

体验AI代码助手

代码解读

复制代码

┌─────────────────────────────┐ │ Node.js 进程 │ ├─────────────────────────────┤ │ ┌─────────────────────┐ │ │ │ JavaScript主线程 │ ← 我们写的代码在这里运行! │ └─────────────────────┘ │ │ │ │ ┌─────────────────────┐ │ │ │ libuv线程池 │ ← 处理文件I/O、DNS等 │ └─────────────────────┘ │ │ │ │ ┌─────────────────────┐ │ │ │ V8后台线程 │ ← 垃圾回收等 │ └─────────────────────┘ │ └─────────────────────────────┘

重点需要理解的内容

  • JavaScript 执行环境是单线程的。
  • Node.js 运行时本身使用了多线程
  • libuv 库提供了线程池来处理某些类型的 I/O 操作

补充知识:

Node.js 的 JavaScript 执行环境确实是单线程的,这意味着你的 JavaScript 代码是在一个主线程中顺序执行的,这个主线程运行着事件循环机制。

然而,Node.js 运行时本身是基于 C++ 的,它内部使用了多线程技术:libuv 这个底层库提供了一个线程池,当 JavaScript 代码执行到某些特定的异步 I/O 操作(如文件系统操作、DNS 查找等)时,这些任务会被提交到 libuv 的线程池中由后台线程执行,从而避免阻塞 JavaScript 主线程;

此外,V8 引擎也会使用一些后台线程来处理垃圾回收等任务。

所以,JavaScript 代码的执行是单线程的,但 Node.js 平台的底层实现是多线程的。

Worker Threads 的工作原理

复制代码

javascript

体验AI代码助手

代码解读

复制代码

const {Worker, isMainThread} = require('worker_threads') if (isMainThread) { // 这是在主线程 console.log('主线程 ID:', process.pid) // 创建新的工作线程 const worker = new Worker( ` const { parentPort } = require('worker_threads'); console.log('工作线程中执行'); parentPort.postMessage('来自工作线程的消息'); `, {eval: true} ) worker.on('message', msg => { console.log('主线程收到:', msg) }) } else { // 这是在工作线程中(这段代码不会在这里执行) }

工作机制

  1. 每个 Worker Thread 都有自己独立的 JavaScript 执行环境
  2. 工作线程与主线程内存不共享(但可以通过 SharedArrayBuffer 共享)
  3. 线程间通过消息传递进行通信

为什么这样设计?

历史演进

  1. 最初设计:专注于 I/O 密集型任务,单线程+事件循环足够高效
  2. 需求变化:JavaScript 应用场景扩展到计算密集型领域
  3. 技术演进:引入 Worker Threads 来弥补单线程的不足

设计哲学

复制代码

javascript

体验AI代码助手

代码解读

复制代码

// 正确的使用方式:主线程负责协调,工作线程负责计算 class TaskManager { async processBigData(data) { // 主线程:任务分发和结果收集 const promises = data.chunks.map(chunk => this.runInWorker('./calculation-worker.js', chunk)) // 不阻塞主线程,可以同时处理其他请求 const results = await Promise.all(promises) return this.aggregateResults(results) } }

这样设计的好处

  • 保持主线程的轻量和响应性
  • 将重型计算卸载到工作线程
  • 既享受单线程的简单性,又获得多线程的计算能力

下面举一些现实开发中的应用 🌰

🌰 例子 1:Web 服务器中的计算任务

复制代码

javascript

体验AI代码助手

代码解读

复制代码

const express = require('express') const {Worker} = require('worker_threads') const app = express() app.get('/fast-request', (req, res) => { // 快速响应,不阻塞 res.json({status: 'ok', message: '立即返回'}) }) app.get('/heavy-calculation', async (req, res) => { // 重型计算交给工作线程 const result = await runInWorker('./heavy-math.js', req.query.data) res.json({result}) }) // 主线程始终保持响应

🌰 例子 2:数据处理管道

复制代码

javascript

体验AI代码助手

代码解读

复制代码

async function processLargeDataset(dataset) { const chunkSize = Math.ceil(dataset.length / 4) // 分成4份 const workers = [] for (let i = 0; i < 4; i++) { const chunk = dataset.slice(i * chunkSize, (i + 1) * chunkSize) workers.push(createWorker('./data-processor.js', chunk)) } // 并行处理,大大加快速度 const results = await Promise.all(workers) return results.flat() }

✍️ 总结

核心要点回顾

  1. Node.js 的单线程指的是 JavaScript 执行环境是单线程的
  2. 底层实现使用了多线程技术来处理 I/O 等操作
  3. Worker Threads 让我们能够在应用层面使用多线程能力
  4. 设计目标是保持【主线程的响应性】,同时获得并行计算的好处

👍 最佳实践

  • 常规 I/O 操作:使用原生 Node.js 单线程 + 异步模式
  • CPU 密集型任务:使用 Worker Threads 避免阻塞主线程
  • 高并发 Web 服务:使用 Cluster 模块充分利用多核 CPU

😎 回答标题的问题

Node.js 既是单线程的,又支持多线程,这并不矛盾:

  • 单线程:指 JavaScript 代码的执行方式,简化编程模型
  • 多线程能力:通过底层库和 Worker Threads 模块提供,解决性能瓶颈

作者:你的人类朋友

链接:https://juejin.cn/post/7557347203514515494

来源:稀土掘金

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

相关推荐
indexsunny13 小时前
互联网大厂Java求职面试实战:Spring Boot微服务与Redis缓存场景解析
java·spring boot·redis·缓存·微服务·消息队列·电商
DBA小马哥13 小时前
时序数据库迁移替换与时序数据库分片
数据库·时序数据库
DBA小马哥13 小时前
时序数据库迁移方案在物联网设备监测中的实践与性能突破
数据库·物联网·时序数据库
ID_1800790547313 小时前
小红书笔记详情API接口基础解析:数据结构与调用方式
数据结构·数据库·笔记
无心水13 小时前
【分布式利器:腾讯TSF】7、TSF高级部署策略全解析:蓝绿/灰度发布落地+Jenkins CI/CD集成(Java微服务实战)
java·人工智能·分布式·ci/cd·微服务·jenkins·腾讯tsf
28岁青春痘老男孩18 小时前
JDK8+SpringBoot2.x 升级 JDK 17 + Spring Boot 3.x
java·spring boot
方璧18 小时前
限流的算法
java·开发语言
元Y亨H18 小时前
Nacos - 服务注册
java·微服务
曲莫终18 小时前
Java VarHandle全面详解:从入门到精通
java·开发语言