SharedArrayBuffer в JavaScript
В этой статье объясняется SharedArrayBuffer
в JavaScript.
Мы подробно объясним основы SharedArrayBuffer
, как использовать его, конкретные примеры применения и аспекты безопасности.
YouTube Video
SharedArrayBuffer в JavaScript
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
В этом примере мы создаем область памяти размером 16 байт с помощью SharedArrayBuffer
и рассматриваем эту память как Int32Array
. Этот буфер памяти может быть использован несколькими потоками.
Использование с Web Workers
Истинная ценность SharedArrayBuffer
проявляется при использовании вместе с Web Workers. Следующий код — пример использования общей памяти между главным потоком и рабочим потоком.
В главном потоке
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 (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 (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.wait
и Atomics.notify
При использовании SharedArrayBuffer
бывают ситуации, когда воркеру необходимо ждать выполнения определенного условия, и как только другой воркер выполнит это условие, он должен уведомить ожидающего. В таких случаях полезны Atomics.wait
и Atomics.notify
.
Atomics.wait
блокирует поток до тех пор, пока значение по конкретному индексу в общей памяти не изменится, а Atomics.notify
уведомляет ожидающие потоки о возможности продолжения работы. Это обеспечивает безопасное ожидание и уведомления между несколькими воркерами. Однако Atomics.wait
не может использоваться в основном потоке и доступен только внутри воркеров.
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
с одним элементом. Этот единственный целочисленный слот используется в качестве сигнала для синхронизации между воркерами. Затем создаются два воркера, и каждому назначается роль с помощью свойстваname
:waiter
(ожидающий) иnotifier
(уведомляющий). В конце общий буфер передаётся обоим воркерам, и настраиваются обработчики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!
- В этом примере воркер
waiter
остаётся в состоянии ожидания с помощьюAtomics.wait
, пока значение по индексу0
равно0
. С другой стороны, когда воркерnotifier
изменяет значение на123
с помощьюAtomics.store
и вызываетAtomics.notify
, воркерwaiter
возобновляет выполнение и может получить обновлённое значение. Таким образом можно реализовать эффективное и безопасное ожидание и уведомление между потоками.
Сферы применения SharedArrayBuffer
SharedArrayBuffer
особенно полезен в следующих случаях:.
- Обработка в реальном времени Это подходит для приложений с низкой задержкой, таких как обработка аудио и видео или игровые движки, где данные должны мгновенно передаваться между потоками.
- Параллельные вычисления
При одновременной обработке больших объемов данных несколькими потоками использование
SharedArrayBuffer
позволяет избежать копирования памяти и повысить производительность. - Машинное обучение Параллелизация задач, таких как предварительная обработка данных и инференс, позволяет выполнять вычисления более эффективно.
Соображения безопасности
SharedArrayBuffer
— это мощная функция, однако она также связана с рисками безопасности. В частности, опасения по поводу атак через побочные каналы, таких как Spectre
, временно приостановили его поддержку. Для уменьшения этой уязвимости браузеры внедрили следующие меры:.
- Изоляция сайтов
Сайты, разрешающие использование
SharedArrayBuffer
, будут запущены в процессе, полностью изолированном от других сайтов. - Политика кросс-доменных ресурсов
Для использования
SharedArrayBuffer
необходимо правильно настроить заголовкиCross-Origin-Opener-Policy
иCross-Origin-Embedder-Policy
.
Например, установив заголовки следующим образом, становится возможным использование SharedArrayBuffer
:.
1Cross-Origin-Opener-Policy: same-origin
2Cross-Origin-Embedder-Policy: require-corp
Это предотвращает вмешательство внешних ресурсов в текущий контент и повышает безопасность.
Резюме
SharedArrayBuffer
— это очень мощный инструмент для совместного использования памяти между несколькими потоками. Это важная технология для повышения производительности, и её эффективность особенно заметна в областях обработки в реальном времени и параллельных вычислений. Однако это также связано с рисками для безопасности, поэтому важны правильная конфигурация и синхронизация.
Используя SharedArrayBuffer
, вы можете создавать более продвинутые и производительные веб-приложения.
Вы можете следовать этой статье, используя Visual Studio Code на нашем YouTube-канале. Пожалуйста, также посмотрите наш YouTube-канал.