SharedArrayBuffer in JavaScript
This article explains SharedArrayBuffer
in JavaScript.
We will provide a detailed explanation of the basics of SharedArrayBuffer
, how to use it, specific use cases, and security considerations.
YouTube Video
SharedArrayBuffer in JavaScript
SharedArrayBuffer
is a powerful tool in JavaScript for sharing memory between multiple threads. Especially in combination with Web Workers, it enables parallel processing, making it effective for computation-intensive tasks and applications requiring real-time capabilities.
What is SharedArrayBuffer?
SharedArrayBuffer
provides a memory buffer in JavaScript that allows sharing binary data between multiple threads (mainly Web Workers). A regular ArrayBuffer
requires copying between the main thread and workers, but SharedArrayBuffer
allows direct memory sharing without copying, thereby significantly improving performance.
Features
- Shared Memory It allows multiple threads to work with the same memory space.
- Performance Improvement Since copying can be omitted, the overhead is reduced when processing large amounts of data.
- Thread Synchronization
You can use it together with
Atomics
to perform synchronization in order to avoid conflicts when accessing memory.
Basic Usage Example
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
In this example, we create a 16-byte memory area using SharedArrayBuffer
and treat that memory area as an Int32Array
. This memory buffer can be shared among multiple threads.
Using with Web Workers
The true value of SharedArrayBuffer
is demonstrated when used in conjunction with Web Workers. The following code is an example of using shared memory between the main thread and a worker.
On the Main Thread
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]);
On the Worker Side (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};
- In this example, the main thread creates a shared buffer and passes it to the worker. The worker can access this buffer to read and modify values. In this way, data can be shared between threads without copying.
Bidirectional Update Confirmation
By using SharedArrayBuffer
, both the main thread and workers can read and write to the same memory, enabling bidirectional update confirmation. Below is an example where the main thread sets a value, a worker changes this value, and then the main thread checks for the update.
On the Main Thread
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};
On the Worker Side (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};
- In this example, the main thread writes the value
100
first, and after the worker reads it, it rewrites it to200
. After that, the worker notifies the main thread, and the main thread reads the shared memory again to confirm the update. In this way, combining notifications enables bidirectional update confirmation.
Synchronization with Atomics
When using shared memory, one must be cautious of data race conditions and inconsistencies. When multiple threads access the same memory simultaneously, conflicts can occur. To prevent this, JavaScript uses the Atomics
object for synchronization.
For example, to safely increment a counter with multiple threads, you can use Atomics
to prevent conflicts.
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
increments the value at a specific index atomically and returns the new value. This operation is guaranteed to be conflict-free with other threads. Atomics.load
is also used to safely read values from shared memory.
Waiting and Notification Using Atomics.wait
and Atomics.notify
When using SharedArrayBuffer
, there are situations where a worker needs to wait until a certain condition is met, and once another worker fulfills that condition, it needs to notify the waiting worker. In such cases, Atomics.wait
and Atomics.notify
are useful.
Atomics.wait
blocks a thread until the value at a specific index in the shared memory changes, while Atomics.notify
notifies waiting threads that they may proceed. This enables safe waiting and notification between multiple workers. However, Atomics.wait
cannot be used on the main thread and is only available inside workers.
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};
- On the main thread, a
SharedArrayBuffer
is created as shared memory and is converted into anInt32Array
with only one element. This single integer slot is used as a signal to synchronize between workers. Next, two workers are created, and each is assigned a role using thename
property:waiter
(the waiting role) andnotifier
(the notifying role). Finally, the shared buffer is passed to both workers, andonmessage
handlers are set up so that messages sent from each worker can be received.
On the Worker Side (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!
- In this example, the
waiter
worker remains in a waiting state usingAtomics.wait
as long as the value at index0
is0
. On the other hand, when thenotifier
worker changes the value to123
withAtomics.store
and callsAtomics.notify
, thewaiter
worker will resume and be able to get the updated value. With this, efficient and safe waiting and notification between threads can be achieved.
Use Cases for SharedArrayBuffer
SharedArrayBuffer
is especially useful for the following use cases:.
- Real-Time Processing It is suitable for applications requiring low latency, such as audio and video processing or game engines, where data needs to be shared instantly between threads.
- Parallel Computing
When processing large amounts of data simultaneously with multiple threads, using
SharedArrayBuffer
avoids memory copying and can improve performance. - Machine Learning By parallelizing tasks such as data preprocessing and inference, efficient computation becomes possible.
Security Considerations
SharedArrayBuffer
is a powerful feature, but it also carries security risks. In particular, concerns about side-channel attacks like Spectre
have temporarily halted its support. To mitigate this vulnerability, browsers have implemented the following measures:.
- Site Isolation
Sites that allow the use of
SharedArrayBuffer
will run in a process that is completely isolated from other sites. - Cross-Origin Resource Policy
To use
SharedArrayBuffer
, theCross-Origin-Opener-Policy
andCross-Origin-Embedder-Policy
headers must be properly set.
For example, by setting headers like the following, the use of SharedArrayBuffer
becomes possible:.
1Cross-Origin-Opener-Policy: same-origin
2Cross-Origin-Embedder-Policy: require-corp
This prevents external resources from interfering with the current content and increases security.
Summary
SharedArrayBuffer
is a very powerful tool for sharing memory between multiple threads. It is an essential technology for improving performance, and its effects are particularly evident in the fields of real-time processing and parallel computing. However, it also involves security risks, so correct configuration and synchronization are important.
By utilizing SharedArrayBuffer
, you can build more advanced and higher-performance web applications.
You can follow along with the above article using Visual Studio Code on our YouTube channel. Please also check out the YouTube channel.