通过 worker_threads 模块,创建独立的 JavaScript 线程(每个线程有独立 V8 和 Event Loop)。线程间可共享内存(SharedArrayBuffer),适合 CPU 密集型任务(如图像处理、大数据计算)。

const { Worker } = require('worker_threads');
new Worker(`
  const { parentPort } = require('worker_threads');
  parentPort.postMessage(calculateCPUIntensiveTask());
`, { eval: true });

SharedArrayBuffer 的底层数据存储(Backing Store)位于进程的 原生堆(Native Heap / C++ Heap)中,而不是 V8 引擎管理的 垃圾回收堆(V8 Managed Heap)。

为了彻底理解这一点,我们需要区分 “进程的内存”“V8 的堆” 以及 “SharedArrayBuffer 的结构”

以下是详细的层级解析:

1. 核心结论:它在哪里?

在 Node.js (以及浏览器) 的 worker_threads 模型中:

  1. 进程级(Process Level): 是的,SharedArrayBuffer 占用的肯定是当前 Node.js 进程的内存地址空间。
  2. V8 引擎级(Engine Level):
    • JS 对象本身(句柄): 每个线程(Worker)中看到的 SharedArrayBuffer 对象实例(即你在 JS 代码中操作的那个变量),是存在于该线程独立的 V8 堆 中的。
    • 真实数据(Backing Store): 这些对象背后指向的那块存储实际二进制数据的内存区域,是分配在 V8 堆之外 的系统原生内存(Native Memory/C++ Heap)中的。

2. 为什么不能在 V8 堆中?

Node.js 的 Worker 实现基于 Isolate(隔离实例) 模型:

  • 独立的 V8 Heap: 每个 Worker 都有自己独立的 V8 实例(Isolate)。Isolate 之间是完全隔离的,A 线程的 V8 垃圾回收器(GC)不能触碰 B 线程的 V8 堆。
  • V8 堆内存不可共享: 如果数据存放在 V8 堆中,内存地址会因为垃圾回收(GC)的整理(Compact)通过移动对象而发生变化,且加上锁机制会极大地影响 GC 性能。

因此,为了实现共享:
V8 使用 C++ 的 malloc(或者操作系统的 mmap)在 原生堆 中分配一块固定的内存区域。所有线程的 V8 Isolate 都持有指向这块原生内存的指针。

3. 图解内存模型

+----------------------- Node.js 进程 (Process) -----------------------+
|                                                                      |
|  [ 操作系统分配的虚拟地址空间 ]                                      |
|                                                                      |
|  +---------------- Thread 1 (Main) ----------------+                 |
|  |  [ V8 Managed Heap ]                            |                 |
|  |  sab_ref = new SharedArrayBuffer() ------------+|                 |
|  |  (这是一个轻量级的 JS 对象/包装器)             | |                |
|  +-------------------------------------------------+ |               |
|                                                      | (指针引用)    |
|  +---------------- Thread 2 (Worker) --------------+ |               |
|  |  [ V8 Managed Heap ]                            | |               |
|  |  workerData_sab --------------------------------+ |               |
|  |  (这是另一个独立的 JS 对象/包装器)             |  |               |
|  +-------------------------------------------------+ |               |
|                                                      | |             |
|          |                                           v v             |
|          |       +-----------------------------------------+         |
|          |       |     Shared Backing Store (底层存储)     |         |
|          +-----> |     位于 Native Heap / C++ Heap         |         |
|                  |     (实际的二进制数据:010101...)       |         |
|                  +-----------------------------------------+         |
|                                                                      |
+----------------------------------------------------------------------+

4. 这种机制带来的影响

A. 生命周期与垃圾回收 (GC)
  • 引用计数: 由于数据不在 V8 堆中,V8 使用类似引用计数的机制来管理这块 Native 内存。
  • 释放时机: 只有当所有共享该内存的线程(Isolate)都对该 SharedArrayBuffer 对象失去了引用,并且这些线程都发生了 GC,导致引用计数归零时,底层的 Native 内存才会被释放。
B. 性能
  • 零拷贝(Zero-Copy): 当你把 SharedArrayBuffer 通过 postMessage 发送给 Worker 时,发送的只是指向那块 Native 内存的指针(以及内存布局信息),而不是复制整个数据块。这对于大数据处理(如图像、视频帧)极快。
  • 访问速度: 访问速度接近 C++ 原生数组,但需要通过 TypedArray(如 Int32Array)或 DataView 来读写。
C. 必须处理并发竞争
  • 因为这块内存是真正的物理共享,V8 不会为你加锁。
  • 如果线程 A 正在写地址 0x001,同时线程 B 也在写 0x001,就会发生竞态条件(Race Condition)
  • 解决方案: 必须使用 Atomics 对象(如 Atomics.add, Atomics.wait, Atomics.notify)来进行原子操作,或者自己实现锁机制。

总结

SharedArrayBuffer数据实体位于进程的 Native Heap(C++ 堆),属于进程内存的一部分,但游离于各个线程独立的 V8 GC 堆之外。这种设计使得多线程能够通过指针直接操作同一块物理内存,从而实现了高效的数据共享。

SharedArrayBuffer 是 JavaScript 中用于 多线程共享内存 的机制,允许不同线程直接读写同一块内存区域。它的概念类似于其他语言(如 Go、Java、C++)中的共享内存多线程编程模型,但实现方式和安全性有所不同。以下是详细对比和解析:

SharedArrayBuffer 与其他语言的共享内存多线程对比

1. SharedArrayBuffer 是什么?

核心特性

  • 共享内存:多个 Worker Threads 可直接访问同一块内存
  • 原子操作:通过 Atomics API 保证线程安全
  • 适用场景:高性能计算(游戏、音视频处理等)

代码示例

// 主线程
const { Worker, isMainThread, SharedArrayBuffer } = require('worker_threads');
const sharedBuffer = new SharedArrayBuffer(16);
const arr = new Uint32Array(sharedBuffer);

if (isMainThread) {
  new Worker(__filename, { workerData: sharedBuffer });
  arr[0] = 1; // 主线程写入
} else {
  console.log(arr[0]); // Worker 线程读取
  Atomics.add(arr, 0, 2); // 原子操作
}

2. 其他语言的共享内存多线程实现

(1) Go(Goroutine + 共享内存)

  • 共享方式:通过 sync.Mutex 或 sync/atomic 包实现线程安全。
  • 区别:Go 的 Goroutine 是轻量级线程(非系统线程),共享内存需显式加锁。
var mutex sync.Mutex

func increment() {
    mutex.Lock()
    counter++
    mutex.Unlock()
}

2. 如何选择共享内存技术?

  • JavaScript:用 SharedArrayBuffer + Worker Threads(适用于 CPU 密集型任务)。

  • Go/Java/C++:原生锁/原子操作(适合高性能后端)。

  • Python:多进程 + 共享内存(受限于 GIL)。

  • Web:WASM + SharedArrayBuffer(跨语言高性能计算)。

总结

SharedArrayBuffer 是 JavaScript 的多线程共享内存方案,类似其他语言的原子变量或锁机制。

Go/Java/C++ 的共享内存更成熟,而 Python 因 GIL 需用多进程。

在 Web 中使用 SharedArrayBuffer 需注意安全策略,适用于 WASM 等高性能场景。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐