Sukka 写前端、网络和基础设施时,习惯先把边界和代价摊开。把「Node.js 多线程 —— worker_threads 初体验 | Sukka's Blog」整理成可落地的中文笔记:问题在哪、默认做法会踩什么坑、该怎么选。原站导航和广告已去掉。
Node.js 是「单线程」的吗?
支持多线程的后端语言常常具有专门的机制在进程之间同步数据,而 Node.js 决定添加多线程支持,就需要一些变通方案了。
在讨论如何编写 Node.js 多线程应用之前,先谈谈 Node.js 本身是不是「单线程」的。答案:是,也可以说不是。
Node.js:Event Loop 和 Worker Pool
打开你的终端,输入 node 然后回车,进入 Node.js 的 REPL 模式。接着打开你的「任务管理器」或者「活动监视器」或者 top (取决于你正在使用的系统),你会发现 node 的进程数可不是 1。
Node.js 启动后的进程数不是 1 甚至远大于 1,不是因为进程池,而是因为 V8 实例是多线程的:
好耶,是 Worker Threads
众所周知,JavaScript 使用 Event Loop 处理主线程。对于 Node.js,耗时操作如 crypto 和 fs (前者 CPU 密集,后者 I/O 密集)在 Worker Pool 中进行。Worker Pool 是 libuv 实现的,其执行模型是单独创建一个进程,在这个进程中同步执行任务,然后将结果返回到 Event Loop 中,Event Loop 可以通过 callback 获取并使用结果。基于这个执行模式我们可以写出这样的代码:
const { readFile } = require ( 'fs' ) ; fs . readFile ( 'path/to/something' , ( err , content ) => { if ( err ) console . error ( err ) ; console . log ( content ) ; } ) ; 以上是一个非阻塞代码的示例,我们不必同步等待某事的发生。只需告诉 Worker Pool 去读取文件,并用结果去调用提供的函数即可。由于 Worker Pool 也有自己的线程,因此 Event Loop 不会被阻塞。
进程间通信
在需要对数据进行复杂计算的时候(比如 AI、机器学习、大数据、或者 OI-Wiki 的 MathJax 公式渲染),主线程(同时也是唯一的线程)会被阻塞以至于不能执行其他任务,因此 Node.js 一般不适合用于执行耗时函数。
如果想要了解更多,你应该看看 Node.js 官网上的这篇文章:「 Don't Block the Event Loop (or the Worker Pool) 」
实现一个 Worker Pool
const { Worker } = require ( 'worker_threads' ) ; const worker = new Worker ( path , { data } ) ; worker . on ( 'message' , msg => { /* … */ } ) ; worker . on ( 'error' , err => { /* … */ } ) ; worker . on ( 'exit' , exitcode => { /* … */ } ) ; worker . on ( 'online' , ( ) => { /* … */ } ) ; 通过创建一个 Worker 类的实例,我们获得了一个 Worker。 Worker 实例的第一个参数 path 是 Workers 的路径,第二个参数 data 是这个 Worker 在启动时可以读到的数据。正如你所见,Worker 之间的通信基于事件,因此我们为事件设置了 Listener 和回调。使用回调是因为
port.postMessage(data[, transferList]); 第一个参数是一个被「 The structured clone algorithm 」复制到另一个线程的对象。一般的,这部分内存会被主线程和 Worker 共用。
实践:使用 Worker Threads 加速 OI-Wiki 的 MathJax 渲染
// worker.js const { parentPort } = require ( 'worker_threads' ) ; parentPort . postMessage ( data ) ; 同样,通过 parentPort 也可以监听从主线程发过来的信息:
// worker.js const { parentPort } = require ( 'worker_threads' ) ; parentPort . on ( 'message' , data => { /* … */ } ) ; 如果要在 Worker 中使用主线程中 new Worker 时第二个参数传入的数据,应该使用 workerData 属性:
值得单独记下的点
- message:Worker 将数据发送到主线程时就会触发 message 事件
- error:Worker 中任何没有被 catch 的 Error 都会触发这一事件,同时 Worker 会被中止
- exit:Worker 退出时触发。在 Worker 中 process.exit() 得到的 exitCode 是 0, worker.terminate() 得到的 exitCode 是 1
- online:当 Worker 解析完毕开始执行时触发
- 创建一个 Workers 索引,以及用于追踪他们是否处于激活状态的索引
- Node.js:Event Loop 和 Worker Pool
- 实践:使用 Worker Threads 加速 OI-Wiki 的 MathJax 渲染
落地时建议先做的 5 件事
- 用自己的流量和设备测,不要只抄厂商推荐最小配置。
- DNS、CDN、代理分流先画清 Fake IP 和 Real IP 的边界。
- 前端性能看 CLS、白屏和重复渲染,而不是只看打包体积。
- 基础设施变更走 Git,能复现、能回滚。
- 结论写成可检查的清单:接口、超时、失败样本、回滚版本。
和智能体产品怎么接
龙虾PRO做 OpenClaw 落地时,网络、DNS 和前端性能笔记最有用的是「边界」:哪一层该加速、哪一层不该假装智能。数字员工调用外部能力前,先把超时和失败路径写死。
本文侧重全链路风控方法论。落地时请用自身业务单据做回放验证,不要把示例阈值直接当生产策略。 相关:风控体检 · 方案资源
常见问题 FAQ
什么是AI智能系统?
「AI智能系统」可概括为:支持多线程的后端语言常常具有专门的机制在进程之间同步数据,而 Node.js 决定添加多线程支持,就需要一些变通方案了。 本文从定义、方法与实践要点展开说明。
为什么要关注AI智能系统?
关注AI智能系统,是因为它直接影响效率、风险与可复制性。文中指出:支持多线程的后端语言常常具有专门的机制在进程之间同步数据,而 Node.js 决定添加多线程支持,就需要一些变通方案了。
如何落地AI智能系统?有哪些关键步骤?
建议按以下路径推进AI智能系统:1) message:Worker 将数据发送到主线程时就会触发 message 事件;2) error:Worker 中任何没有被 catch 的 Error 都会触发这一事件,同时 Worker 会被中止;3) exit:Worker 退出时触发。在 Worker 中 process.exit() 得到的 exitCode 是 0, worker.terminate(…;4) online:当 Worker 解析完毕开始执行时触发;5) 创建一个 Workers 索引,以及用于追踪他们是否处于激活状态的索引。细节见正文对应章节。
AI智能系统适合哪些人或团队?
AI智能系统更适合:产品/技术负责人、运营与增长团队、需要落地智能体或自动化的中小团队、关注「AI智能系统」方向的读者。若你只需要单次聊天式问答,可先读概念;若要上生产,请重点看步骤、权限与风控相关段落。
关于「Node.js 是「单线程」的吗?」,本文给出了什么结论?
在「Node.js 是「单线程」的吗?」部分,要点是:你的终端,输入 node 然后回车,进入 Node.js 的 REPL 模式。接着打开你的「任务管理器」或者「活动监视器」或者 top (取决于你正在使用的系统),你会发现 node 的进程数可不是 1。 Node.js 启动后的进程数不是 1 甚至远大于 1,不是因为进程池,而是因为 V8 实例是多线程的: 好耶,是 Worker Threads 众所周知,JavaScript 使用 Event Loop 处理主线程。对于 Node.
关于「Node.js:Event Loop 和 Worker Pool」,本文给出了什么结论?
在「Node.js:Event Loop 和 Worker Pool」部分,要点是:Loop 可以通过 callback 获取并使用结果。基于这个执行模式我们可以写出这样的代码: const { readFile } = require ( 'fs' ) ; fs . readFile ( 'path/to/something' , ( err , content ) => { if ( err ) console . error ( err ) ; console . log ( content ) ; } ) ;