【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

来源:稀土掘金

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

相关推荐
salipopl1 分钟前
Spring Boot 整合 Druid 并开启监控
java·spring boot·后端
dovens2 分钟前
PostgreSQL 中进行数据导入和导出
大数据·数据库·postgresql
IOT.FIVE.NO.12 分钟前
claude code desktop cowork报错解决和记录Workspace..The isolated Linux environment ...
linux·服务器·数据库
辛苦才能6 分钟前
数据结构--排序--插入排序(C语言,重点排序面试和比赛都会考察)
c语言·数据结构·面试
ShiJiuD6668889997 分钟前
JSP Cookie和Session
java·开发语言
Rick199310 分钟前
mysql 慢查询怎么快速定位
android·数据库·mysql
GISer_Jing11 分钟前
AI原生前端工程化进阶实践:从流式交互架构到端云协同全链路落地
前端·人工智能·后端·学习
geNE GENT12 分钟前
Spring Boot 实战篇(四):实现用户登录与注册功能
java·spring boot·后端
952367 小时前
MyBatis
后端·spring·mybatis
科技小花7 小时前
全球化深水区,数据治理成为企业出海 “核心竞争力”
大数据·数据库·人工智能·数据治理·数据中台·全球化