TypeScript 中的 `SharedArrayBuffer`
本文解释了 TypeScript 中的 SharedArrayBuffer。
我们将通过实际示例来讲解在TypeScript中使用SharedArrayBuffer。
YouTube Video
TypeScript 中的 SharedArrayBuffer
SharedArrayBuffer 是一种允许多个线程(比如 Web Workers)共享同一内存空间的机制。结合使用Atomics,可以管理数据竞争并执行低延迟的共享内存操作。
前提条件与注意事项
在浏览器中使用SharedArrayBuffer时,需要设置COOP和COEP头部,以满足称为跨域隔离的安全要求。在Node.js中,可以通过worker_threads较为简单地处理共享内存。
基本概念
SharedArrayBuffer是一个表示固定长度字节序列的对象,可以通过TypedArray等视图来读写数值。简单的读写操作并不同步,因此需要使用Atomics API来保证原子性操作,并通过wait和notify机制进行协调。
简单计数器(浏览器版)
在本示例中,主线程创建一个SharedArrayBuffer并传递给Web Worker,两者共同增加一个共享计数器。该示例展示了最基本的模式:用Atomics.add进行原子加法,用Atomics.load读取值。
main.ts(浏览器端)
本示例演示了主线程如何创建SharedArrayBuffer并与Worker共享,实现多线程访问。
1// main.ts
2// Create a 4-byte (Int32) shared buffer for one counter
3const sab = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 1);
4const counter = new Int32Array(sab); // view over the buffer
5
6// Create worker and transfer the SharedArrayBuffer
7const worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' });
8worker.postMessage({ sab });
9
10// Increase counter in main thread every 200ms
11setInterval(() => {
12 const newVal = Atomics.add(counter, 0, 1) + 1; // Atomically increment
13 console.log('Main incremented ->', newVal);
14}, 200);
15
16// Listen for messages (optional)
17worker.onmessage = (e) => {
18 console.log('From worker:', e.data);
19};- 在此代码中,主线程通过
Atomics.add原子性地增加值。在worker.ts一侧,也可以访问和操作同一个SharedArrayBuffer。
worker.ts(浏览器 Worker)
这是一个Worker接收同一共享缓冲区并定期减少或操作它的示例。
1// worker.ts
2self.onmessage = (ev: MessageEvent) => {
3 const { sab } = ev.data as { sab: SharedArrayBuffer };
4 const counter = new Int32Array(sab);
5
6 // Every 350ms, atomically add 2 (demonstration)
7 setInterval(() => {
8 const newVal = Atomics.add(counter, 0, 2) + 2;
9 // Optionally notify main thread (postMessage)
10 self.postMessage(`Worker added 2 -> ${newVal}`);
11 }, 350);
12};- Worker同样通过
Int32Array操作同一块内存,并且由于Atomics的存在,更新不会出现竞争条件。
使用wait/notify进行同步
通过使用Atomics.wait和Atomics.notify,可以让线程挂起直到满足特定条件,从而实现在Worker线程内的事件驱动同步。在浏览器中,最安全的做法是在Worker内部使用Atomics.wait。
producer.ts(浏览器生产者Worker)
生产者写入数据并通过notify通知消费者。
1// producer.ts (worker)
2self.onmessage = (ev: MessageEvent) => {
3 const { sab } = ev.data as { sab: SharedArrayBuffer };
4 const state = new Int32Array(sab); // state[0] will be 0=empty,1=filled
5
6 // produce every 500ms
7 setInterval(() => {
8 // produce: set state to 1 (filled)
9 Atomics.store(state, 0, 1);
10 Atomics.notify(state, 0, 1); // wake one waiter
11 self.postMessage('produced');
12 }, 500);
13};consumer.ts(浏览器消费者Worker)
消费者通过Atomics.wait等待,并在收到生产者通知后继续处理。
1// consumer.ts (worker)
2self.onmessage = (ev: MessageEvent) => {
3 const { sab } = ev.data as { sab: SharedArrayBuffer };
4 const state = new Int32Array(sab);
5
6 // consumer loop
7 (async function consumeLoop() {
8 while (true) {
9 // if state is 0 => wait until notified (value changes)
10 while (Atomics.load(state, 0) === 0) {
11 // Blocks this worker until notified or timeout
12 Atomics.wait(state, 0, 0);
13 }
14 // consume (reset to 0)
15 // (processing simulated)
16 // read data from another shared buffer in real scenarios
17 Atomics.store(state, 0, 0);
18 // continue loop
19 self.postMessage('consumed');
20 }
21 })();
22};- 在这种模式下,生产者通过
Atomics.notify通知消费者,消费者通过Atomics.wait高效地等待。在浏览器中,主线程无法调用Atomics.wait。为了防止UI冻结,Atomics.wait仅允许在Worker中使用。
Node.js(worker_threads)实用示例
在Node.js环境中,可以通过worker_threads使用SharedArrayBuffer相关功能。以下为Node.js环境下带类型的TypeScript示例。
main-node.ts
主线程创建缓冲区并传递给Worker。
1// main-node.ts
2import { Worker } from 'worker_threads';
3import path from 'path';
4
5// Create SharedArrayBuffer for one 32-bit integer
6const sab = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT);
7const counter = new Int32Array(sab);
8
9// Spawn worker and pass sab via workerData
10const worker = new Worker(path.resolve(__dirname, 'worker-node.js'), {
11 workerData: { sab }
12});
13
14// Log messages from worker
15worker.on('message', (msg) => console.log('Worker:', msg));
16
17// Increment in main thread periodically
18setInterval(() => {
19 const val = Atomics.add(counter, 0, 1) + 1;
20 console.log('Main increment ->', val);
21}, 300);worker-node.ts
此示例在Worker端使用了worker_threads中的parentPort和workerData。
1// worker-node.ts
2import { parentPort, workerData } from 'worker_threads';
3
4const sab = workerData.sab as SharedArrayBuffer;
5const counter = new Int32Array(sab);
6
7// Periodically add 5
8setInterval(() => {
9 const val = Atomics.add(counter, 0, 5) + 5;
10 parentPort?.postMessage(`Added 5 -> ${val}`);
11}, 700);- 在Node.js中,没有浏览器中的COOP或COEP限制,因此只需使用
worker_threads即可轻松实现共享内存。在使用TypeScript时,请注意是采用CommonJS还是ESM设置进行构建。
TypeScript 类型要点
SharedArrayBuffer和Atomics已包含在标准的DOM及库类型定义中,因此可以直接在TypeScript中使用。与Worker交换消息时,明确定义接口与类型更为安全。
1// Example: typed message
2type WorkerMessage = { type: 'init'; sab: SharedArrayBuffer } | { type: 'ping' };- 显式定义类型能使
postMessage和onmessage处理更加安全,并实现类型检查。
实际应用场景
SharedArrayBuffer 并非总是必需的,但在需要共享内存带来的速度优势时非常有效。了解其适用场景有助于做出合适的技术选择。
- 适用于需要高速共享缓冲区的低延迟处理,如实时音视频处理或游戏物理计算。
- 对于简单数据交换或大量数据传输,
Transferable ArrayBuffer或postMessage可能比SharedArrayBuffer更容易使用。
限制与安全性
在浏览器中使用SharedArrayBuffer时需要跨域隔离:COOP设置为same-origin-allow-popups,COEP设置为require-corp。如果不满足这些要求,SharedArrayBuffer将被禁用。
性能与调试建议
原子操作(Atomics)很快,但频繁的等待和过度同步会增加延迟。
为安全高效地处理共享内存,应注意以下事项。
- 如
Int32Array等视图应正确处理字节对齐。 - 明确同一个共享缓冲的哪些索引由哪些进程使用,并在代码中保持一致规范。
- 如果像普通
ArrayBuffer一样对待SharedArrayBuffer,会发生数据竞争。未使用Atomics便从多个线程写入同一索引是很危险的。
总结
合理使用SharedArrayBuffer和Atomics,即使在TypeScript中也能实现安全快速的共享内存操作。从计数器、信号等简单同步模式入门较易理解,并且只要管理好正确的同步与索引,即使在低延迟场景下也能高效应对。
您可以在我们的YouTube频道上使用Visual Studio Code跟随上述文章进行学习。 请也查看我们的YouTube频道。