JavaScript中的SharedArrayBuffer

JavaScript中的SharedArrayBuffer

本文将解释JavaScript中的SharedArrayBuffer

我们将详细讲解 SharedArrayBuffer 的基础知识、使用方法、具体使用场景以及安全考虑。

YouTube Video

JavaScript中的SharedArrayBuffer

SharedArrayBuffer是JavaScript中用于在多个线程之间共享内存的强大工具。特别是结合Web Workers使用时,它支持并行处理,使其在处理计算密集型任务和需要实时能力的应用中非常高效。

什么是SharedArrayBuffer?

SharedArrayBuffer在JavaScript中提供了一种内存缓冲区,允许在多个线程之间共享二进制数据(主要是Web Workers)。普通的ArrayBuffer需要在线程主线程和工作线程之间进行复制,而SharedArrayBuffer则允许直接内存共享,无需复制,从而显著提升了性能。

功能

  • 共享内存 它允许多个线程在同一内存空间中工作。
  • 性能提升 由于可以省略复制,在处理大量数据时可减少开销。
  • 线程同步 可以与 Atomics 一起使用进行同步,以避免访问内存时发生冲突。

基本使用示例

 1// Create a 16-byte shared memory
 2const sharedBuffer = new SharedArrayBuffer(16);
 3
 4// Treat it as an Int32Array
 5const sharedArray = new Int32Array(sharedBuffer);
 6
 7// Set a value
 8sharedArray[0] = 42;
 9
10console.log(sharedArray[0]);  // 42

在这个示例中,我们使用SharedArrayBuffer创建一个16字节的内存区,并将该内存区作为Int32Array处理。该内存缓冲区可以在多个线程之间共享。

与Web Workers结合使用

当与Web Workers结合使用时,SharedArrayBuffer的真正价值得以体现。以下代码是主线程和工作线程之间使用共享内存的示例。

在主线程上

 1// Create a shared buffer
 2const sharedBuffer = new SharedArrayBuffer(16);
 3const sharedArray = new Int32Array(sharedBuffer);
 4
 5// Create a worker
 6const worker = new Worker('worker.js');
 7
 8// Pass the shared buffer to the worker
 9worker.postMessage(sharedBuffer);
10
11// Modify the memory
12// Output : Main thread: 100
13sharedArray[0] = 100;
14console.log("Main thread: ", sharedArray[0]);

在工作线程端(worker.js

 1// worker.js
 2self.onmessage = function(event) {
 3    // Use the received shared buffer
 4    const sharedArray = new Int32Array(event.data);
 5
 6    // Read the contents of the memory
 7    // Output : Worker thread: 100
 8    console.log("Worker thread: ", sharedArray[0]);
 9
10    // Change the value
11    sharedArray[0] = 200;
12};
  • 在这个示例中,主线程创建了一个共享缓冲区并将其传递给工作线程。工作线程可以访问该缓冲区以读取和修改值。通过这种方式,可以在线程之间共享数据而无需复制。

双向更新确认

通过使用 SharedArrayBuffer,主线程和工作线程都可以读写同一块内存,实现双向更新确认。以下是一个示例:主线程设置一个值,工作线程修改该值,然后主线程检查更新。

在主线程上

 1// Create a shared buffer
 2const sharedBuffer = new SharedArrayBuffer(16);
 3const sharedArray = new Int32Array(sharedBuffer);
 4
 5// Create a worker
 6const worker = new Worker('worker.js');
 7
 8// Pass the shared buffer to the worker
 9worker.postMessage(sharedBuffer);
10
11// Set initial value
12// Output : Main thread initial: 100
13sharedArray[0] = 100;
14console.log("Main thread initial:", sharedArray[0]);
15
16// Listen for worker confirmation
17worker.onmessage = () => {
18    // Output : Main thread after worker update: 200
19    console.log("Main thread after worker update:", sharedArray[0]);
20};

在工作线程端(worker.js

 1// worker.js
 2self.onmessage = function(event) {
 3    const sharedArray = new Int32Array(event.data);
 4
 5    // Read initial value
 6    // Output : Worker thread received: 100
 7    console.log("Worker thread received:", sharedArray[0]);
 8
 9    // Update the value
10    sharedArray[0] = 200;
11
12    // Notify main thread
13    self.postMessage("Value updated");
14};
  • 在此示例中,主线程首先写入值 100,工作线程读取后将其改为 200。之后,工作线程通知主线程,主线程再次读取共享内存以确认更新。通过结合通知,可以实现双向更新确认。

使用Atomics进行同步

在使用共享内存时,必须注意数据竞争条件和不一致性。当多个线程同时访问相同的内存时,可能会发生冲突。为防止这种情况,JavaScript 使用 Atomics 对象进行同步。

例如,为了在多个线程中安全地递增一个计数器,可以使用 Atomics 防止冲突。

1const sharedBuffer = new SharedArrayBuffer(16);
2const sharedArray = new Int32Array(sharedBuffer);
3
4// Increment the counter
5Atomics.add(sharedArray, 0, 1);
6
7console.log(Atomics.load(sharedArray, 0));  // 1

Atomics.add 可以以原子操作的方式递增特定索引处的值,并返回新值。此操作可以确保与其他线程之间无冲突。Atomics.load 也被用于安全地从共享内存中读取值。

使用 Atomics.waitAtomics.notify 进行等待与通知

在使用 SharedArrayBuffer 时,某些情况下需要让一个工作线程等待某个条件成立,而当另外一个线程满足条件后,需要通知正在等待的线程。此时,Atomics.waitAtomics.notify 就非常有用。

Atomics.wait 会让线程等待直到共享内存中特定索引的值发生变化,而 Atomics.notify 会通知等待中的线程可以继续执行。这样可以在多个工作线程之间安全地实现等待与通知。但是,Atomics.wait 不能在主线程中使用,只能在 worker 内部使用

 1// Create a shared buffer (1 Int32 slot is enough for signaling)
 2const sharedBuffer = new SharedArrayBuffer(4);
 3const sharedArray = new Int32Array(sharedBuffer);
 4
 5// Create workers with names
 6const waiter = new Worker('worker.js', { name: 'waiter' });
 7const notifier = new Worker('worker.js', { name: 'notifier' });
 8
 9// Pass the shared buffer to both
10waiter.postMessage(sharedBuffer);
11notifier.postMessage(sharedBuffer);
12
13// Listen for messages
14waiter.onmessage = (event) => {
15    console.log(`[Main] Message from waiter:`, event.data);
16};
17notifier.onmessage = (event) => {
18    console.log(`[Main] Message from notifier:`, event.data);
19};
  • 在主线程中,创建了一个 SharedArrayBuffer 作为共享内存,并将其转换为只包含一个元素的 Int32Array。这个单一的整数槽用作在 worker 之间同步的信号。接下来,创建了两个 worker,并通过 name 属性为每个 worker 分配了一个角色:waiter(等待者)和 notifier(通知者)。最后,将共享缓冲区传递给两个 worker,并设置了 onmessage 处理程序,以便能够接收来自每个 worker 的消息。

在工作线程端(worker.js

 1// worker.js
 2onmessage = (event) => {
 3    const sharedArray = new Int32Array(event.data);
 4
 5    if (self.name === 'waiter') {
 6        postMessage('Waiter is waiting...');
 7        // Wait until notifier signals index 0
 8        Atomics.wait(sharedArray, 0, 0);
 9        postMessage('Waiter was notified!');
10    }
11
12    if (self.name === 'notifier') {
13        postMessage('Notifier is preparing...');
14        setTimeout(() => {
15            // Notify waiter after 2 seconds
16            Atomics.store(sharedArray, 0, 1);
17            Atomics.notify(sharedArray, 0, 1);
18            postMessage('Notifier has sent the signal!');
19        }, 2000);
20    }
21};
22// Output
23// [Main] Message from waiter: Waiter is waiting...
24// [Main] Message from notifier: Notifier is preparing...
25// [Main] Message from notifier: Notifier has sent the signal!
26// [Main] Message from waiter: Waiter was notified!
  • 在此示例中,只要索引为 0 的值为 0waiter worker 就会使用 Atomics.wait 保持等待状态。另一方面,当 notifier worker 使用 Atomics.store 将值更改为 123 并调用 Atomics.notify 后,waiter worker 会恢复并能够获取到更新后的值。通过这种方式,可以实现高效且安全的线程间等待与通知

SharedArrayBuffer 的使用场景

SharedArrayBuffer 特别适用于以下使用场景:。

  • 实时处理 适用于需要低延迟的应用场景,如音视频处理或游戏引擎,需要在线程之间实时共享数据。
  • 并行计算 在多个线程同时处理大量数据时,使用 SharedArrayBuffer 可以避免内存复制,提高性能。
  • 机器学习 通过并行化数据预处理和推断等任务,可以实现高效计算。

安全性考虑

SharedArrayBuffer 是一个强大的功能,但也存在安全风险。特别是对像 Spectre 这样的侧信道攻击的担忧,导致其支持一度被暂时中止。为了缓解这一漏洞,浏览器已经实施了以下措施:。

  • 站点隔离 允许使用 SharedArrayBuffer 的站点将运行在与其他站点完全隔离的进程中。
  • 跨源资源策略 要使用 SharedArrayBuffer,必须正确设置 Cross-Origin-Opener-PolicyCross-Origin-Embedder-Policy 头信息。

例如,通过设置如下头信息,可以启用 SharedArrayBuffer 的使用:。

1Cross-Origin-Opener-Policy: same-origin
2Cross-Origin-Embedder-Policy: require-corp

这可以防止外部资源干扰当前内容,并提高安全性。

总结

SharedArrayBuffer 是一个在多线程之间共享内存的非常强大的工具。这是提高性能的一项重要技术,其效果在实时处理和并行计算领域尤为显著。然而,这也涉及到安全风险,因此正确的配置和同步非常重要。

通过利用 SharedArrayBuffer,可以构建更加先进和高性能的 Web 应用程序。

您可以在我们的YouTube频道上使用Visual Studio Code跟随上述文章进行学习。 请也查看我们的YouTube频道。

YouTube Video