Node.js中如何在主线程与Worker线程间共享大变量?
Node.js主线程与Worker线程共享大JSON数组的高效方案(基于Piscina)
针对你遇到的100+MB JSON数组无法在主线程与Worker线程高效共享的问题,以下两种方案可以避免重复克隆或重复读取文件的性能损耗:
方案一:使用SharedArrayBuffer实现内存共享
通过将JSON数据序列化为二进制格式存入SharedArrayBuffer,可以让主线程和所有Worker线程共享同一块内存区域,彻底避免数据克隆带来的开销。
修改后的代码:
main.js
const piscina = require('piscina'); const { resolve } = require('path'); const fs = require('fs'); // 读取并序列化大JSON文件为二进制 const jsonStr = fs.readFileSync('./heavy-file.json', 'utf8'); const encoder = new TextEncoder(); const uint8Array = encoder.encode(jsonStr); // 创建SharedArrayBuffer存储二进制数据 const sharedBuffer = new SharedArrayBuffer(uint8Array.byteLength); const sharedUint8Array = new Uint8Array(sharedBuffer); sharedUint8Array.set(uint8Array); async function runTasks() { const argLst = [1, 2, 3]; const pool = new piscina({ filename: resolve(__dirname, 'worker.js'), // 将SharedArrayBuffer作为workerData传递给所有Worker workerData: sharedBuffer }); const requests = []; for (let arg of argLst) { requests.push(pool.run(arg)); } await Promise.all(requests); } runTasks();
worker.js
const { workerData } = require('worker_threads'); async function task(arg) { // 从SharedArrayBuffer反序列化出JSON对象 const decoder = new TextDecoder(); const jsonStr = decoder.decode(new Uint8Array(workerData)); const heavyVar = JSON.parse(jsonStr); let result = heavyVar.filter(r => r.prop == arg); console.log(result); return result; } module.exports = task;
方案二:利用Worker模块初始化特性,每个Worker仅加载一次文件
Piscina的每个Worker线程会独立加载模块文件,我们可以在Worker模块的顶层(而非任务函数内)读取JSON文件,这样每个Worker线程只会在启动时读取一次文件,后续任务直接复用已加载的内存数据,避免重复读取或传递的开销。
修改后的代码:
main.js
const piscina = require('piscina'); const { resolve } = require('path'); async function runTasks() { const argLst = [1, 2, 3]; const pool = new piscina({ filename: resolve(__dirname, 'worker.js') }); const requests = []; for (let arg of argLst) { requests.push(pool.run(arg)); } await Promise.all(requests); } runTasks();
worker.js
// 在Worker模块顶层加载JSON,每个Worker仅加载一次 const heavyVar = require('./heavy-file.json'); async function task(arg) { let result = heavyVar.filter(r => r.prop == arg); console.log(result); return result; } module.exports = task;
方案对比
- 方案一:内存占用最低(仅一份数据),适合超大文件或线程池数量较多的场景;但需要额外的序列化/反序列化步骤,有少量性能开销。
- 方案二:实现最简单,无需额外转换逻辑;但每个Worker线程会保留一份数据副本,内存占用为
线程池大小 × 文件大小,适合线程池规模较小的场景。
内容的提问来源于stack exchange,提问作者sprash
相关产品推荐
相关产品推荐

