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

作者:你的人类朋友日期:2025/10/7

前言

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

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

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

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

事件循环(Event Loop)机制

1// 这是一个简单的 Node.js 程序
2console.log('开始执行')
3
4setTimeout(() => {
5    console.log('定时器回调')
6}, 1000)
7
8console.log('继续执行')
9
10// 输出顺序:
11// 开始执行
12// 继续执行
13// 定时器回调
14

核心特点

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

单线程的优势

1// 单线程模型简单易懂
2let count = 0
3
4function increment() {
5    count++
6    console.log(count)
7}
8
9increment() // 输出 1
10increment() // 输出 2
11// 不用担心多线程的竞争条件问题
12

优点

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

那为什么还需要多线程?

单线程的局限性

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

问题暴露

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

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

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

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

1┌─────────────────────────────┐
2│         Node.js 进程         │
3├─────────────────────────────┤
4│  ┌─────────────────────┐    │
5│  │    JavaScript主线程   │ ← 我们写的代码在这里运行!
6│  └─────────────────────┘    │
7│                             │
8│  ┌─────────────────────┐    │
9│  │    libuv线程池       │ ← 处理文件I/O、DNS等
10│  └─────────────────────┘    │
11│                             │
12│  ┌─────────────────────┐    │
13│  │   V8后台线程         │ ← 垃圾回收等
14│  └─────────────────────┘    │
15└─────────────────────────────┘
16

重点需要理解的内容

  • 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 的工作原理

1const {Worker, isMainThread} = require('worker_threads')
2
3if (isMainThread) {
4    // 这是在主线程
5    console.log('主线程 ID:', process.pid)
6
7    // 创建新的工作线程
8    const worker = new Worker(
9        `
10        const { parentPort } = require('worker_threads');
11        console.log('工作线程中执行');
12        parentPort.postMessage('来自工作线程的消息');
13    `,
14        {eval: true}
15    )
16
17    worker.on('message', msg => {
18        console.log('主线程收到:', msg)
19    })
20} else {
21    // 这是在工作线程中(这段代码不会在这里执行)
22}
23

工作机制

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

为什么这样设计?

历史演进

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

设计哲学

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

这样设计的好处

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

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

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

1const express = require('express')
2const {Worker} = require('worker_threads')
3const app = express()
4
5app.get('/fast-request', (req, res) => {
6    // 快速响应,不阻塞
7    res.json({status: 'ok', message: '立即返回'})
8})
9
10app.get('/heavy-calculation', async (req, res) => {
11    // 重型计算交给工作线程
12    const result = await runInWorker('./heavy-math.js', req.query.data)
13    res.json({result})
14})
15
16// 主线程始终保持响应
17

🌰 例子 2:数据处理管道

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

✍️ 总结

核心要点回顾

  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 模块提供,解决性能瓶颈

【Node】单线程的Node.js为什么可以实现多线程?》 是转载文章,点击查看原文


相关推荐


Vue2 动态添加属性导致页面不更新的原因与解决方案
excel2025/10/6

在 Vue2 开发中,经常会遇到这样一个问题:对象新增属性后,数据虽然更新了,但页面并没有随之更新。本文将通过一个例子来说明原因,并给出解决方案。 一、问题示例 我们先来看一个简单的例子: <div id="app"> <p v-for="(value, key) in item" :key="key"> {{ value }} </p> <button @click="addProperty">动态添加新属性</button> </div> Vue 实例代码如下: co


重新定义创意边界:Seedream 4.0深度测评——从个人创作到企业级生产的AI图像革命
一个天蝎座白勺程序猿2025/10/4

一、引言:AI图像创作的“奇点时刻”” 2025年的AI赛道,图像生成领域正经历一场“效率革命”。从Midjourney的写实风格到DALL·E 3的语义理解,技术迭代速度远超行业预期。然而,用户痛点始终存在: 创作流程割裂:生成、编辑、排版需切换多个工具,设计师日均耗时超3小时在“导出-导入”的重复操作中;一致性失控:多图合成时,人物比例、光影逻辑、风格统一性常需手动修正,电商海报批量生产效率低下;企业部署门槛高:私有化部署成本高昂,API调用缺乏行业适配方案,中小团队难以规模化应用。


使用Claude Code Router轻松切换各种高性价比模型
小溪彼岸2025/10/3

前言 前段时间随着Claude Code CLI的爆火也随之火了一款Claude Code CLI扩展Claude Code Router,该扩展工具可以很方便的将各大主流模型接入到Claude Code CLI中使用(那段时间国内各大模型还没有支持Claude Code CLI,Claude Code CLI只能使用Claude Code模型),今天我们也来了解一下这款神奇的工具。对往期内容感兴趣的小伙伴也可以看往期内容: Claude Code CLI初体验 不习惯终端黑窗口?Claude


Scrapy 重构新选择:scrapy_cffi 快速上手教程
两只好2025/10/2

随着爬虫场景的不断升级,Scrapy 虽然成熟稳定,但在异步支持、WebSocket 和现代请求库等方面有一些局限。 scrapy_cffi 是在 Scrapy 风格基础上重构的异步爬虫框架,支持更现代的请求库、扩展机制和异步 DB/MQ 管理。 通过这篇教程,你可以快速创建自己的异步爬虫项目,并体验框架的核心特性。 1.为什么要重构 Scrapy Scrapy 本身虽然功能强大,但存在一些痛点: IDE 提示有限:代码提示和补全不够友好 异步支持弱:asyncio 协程能力有限,WebSo


云原生周刊:K8s 故障排查秘籍
KubeSphere 云原生2025/10/2

云原生热点 Perses v0.52.0 发布 Perses 是一个面向可观测性(observability)的开源仪表盘 / 可视化工具,作为 CNCF 的 Sandbox 级别项目。 近日,Perses 宣布了其 0.52.0 版本的发布,带来了多个重大特性与增强,其中包括:对持续性能剖析(continuous profiling)的支持(新增 Pyroscope 数据源插件与 Flame Chart 可视化面板)、日志探索能力(Loki 数据源插件 + 日志面板)、Prometheu


分布式计数器系统完整解决方案
nlog3n10/2/2025

多级缓存架构:本地缓存 + Redis集群 + 数据库,实现性能与可靠性平衡智能分片策略:根据热度动态调整分片数量,解决热点key问题异步数据同步:通过消息队列实现最终一致性,提升写入性能完善的限流防刷:多维度限流 + 用户行为校验,防止恶意攻击强大的容灾能力:自动故障检测、优雅降级、数据恢复机制系统可支持百万级并发,响应时间控制在10ms以内,可用性达到99.99%以上,完全满足大型互联网产品的需求。关键创新点基于访问频率的智能分片算法多级缓存的优雅降级机制操作日志的数据恢复方案。


iOS 26 能耗检测实战指南,升级后电池掉速是否正常 + KeyMob + Instruments 实时监控 + 优化策略
程序员不说人话10/1/2025

本文聚焦 iOS 26 能耗检测,分析系统升级初期耗电风险、Liquid Glass 视觉效果对电池的额外负荷、Adaptive Power 模式机制,介绍如何用 KeyMob + Instruments 记录电量曲线 /功率峰值 /负载指标,定位高耗电模块并优化方案。


Redisson和Zookeeper实现的分布式锁
getdu9/30/2025

可以使用红锁来解决不一致问题,建立多个主节点当获取锁成功的数量n/2+1及以上才算获取锁成功。我觉得就是一个排队,创建节点后看自己是不是最小,不是最小就监听前一个节点,是最小就获取锁成功,锁释放以后,zookeeper会通过Watcher通知当前客户端。客户端获取 /locks/my_lock 目录下所有的子节点,并按节点序号排序。客户端被唤醒后,回到第 2 步,重新检查自己是否变成了最小节点。如果自己不是最小节点,客户端就找到比自己序号小的前一个节点。如果自己创建的节点是序号最小的那个,则成功获取锁。


第8章:定时任务与触发器——让 Bot 主动服务
芝麻开门-新起点2025/10/8

8.1 什么是定时任务? 在之前的章节中,我们的 Bot 都是被动响应用户的输入。用户提问,Bot 回答。但很多时候,我们希望 Bot 能够主动在特定时间执行任务,例如每天早上发送天气预报、定时提醒用户喝水、或者定期从网站抓取数据并汇报。这就是定时任务 (Scheduled Task) 的用武之地。 Coze 中的定时任务功能,允许你设置一个触发器 (Trigger),当满足预设的时间条件时,自动运行指定的 Bot 或工作流。这极大地扩展了 Bot 的应用场景,使其从一个问答工具变成了一个可以


突破速度障碍:非阻塞启动画面如何将Android 应用启动时间缩短90%
稀有猿诉2025/10/10

本文译自「Breaking the Speed Barrier: How Non-Blocking Splash Screens Cut Android App Launch Time by 90%」,原文链接sankalpchauhan.com/breaking-th…,由Sankalp Chauhan发布于2025年9月28日。 概述 正值佳节期间,我们在每个应用上都能看到精美的启动画面和自定义徽标。在开发这些应用时,每个 Android 开发者都会面临启动画面的困境:用户期望获得美观且品

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0