SharedArrayBuffer in JavaScript
Dieser Artikel erklärt SharedArrayBuffer
in JavaScript.
Wir werden eine detaillierte Erklärung der Grundlagen von SharedArrayBuffer
geben, wie man es verwendet, spezifische Anwendungsfälle und Sicherheitsüberlegungen.
YouTube Video
SharedArrayBuffer in JavaScript
SharedArrayBuffer
ist ein leistungsstarkes Werkzeug in JavaScript, um Speicher zwischen mehreren Threads zu teilen. Insbesondere in Kombination mit Web-Worker ermöglicht es parallele Verarbeitung und ist somit effektiv für rechenintensive Anwendungen und Aufgaben mit Echtzeit-Anforderungen.
Was ist SharedArrayBuffer?
SharedArrayBuffer
stellt einen Speicherpuffer in JavaScript bereit, der das Teilen von Binärdaten zwischen mehreren Threads (hauptsächlich Web-Worker) ermöglicht. Ein regulärer ArrayBuffer
erfordert das Kopieren zwischen dem Hauptthread und den Workern, während SharedArrayBuffer
direktes Teilen des Speichers ohne Kopieren ermöglicht und so die Leistung erheblich verbessert.
Merkmale
- Gemeinsamer Speicher Es ermöglicht mehreren Threads, mit demselben Speicherbereich zu arbeiten.
- Leistungsverbesserung Da das Kopieren entfallen kann, wird der Overhead beim Verarbeiten großer Datenmengen reduziert.
- Thread-Synchronisierung
Sie können es zusammen mit
Atomics
verwenden, um eine Synchronisierung durchzuführen und so Konflikte beim Zugriff auf den Speicher zu vermeiden.
Grundlegendes Anwendungsbeispiel
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 diesem Beispiel erstellen wir einen 16-Byte-Speicherbereich mit SharedArrayBuffer
und behandeln diesen Speicherbereich als Int32Array
. Dieser Speicherpuffer kann zwischen mehreren Threads geteilt werden.
Verwendung mit Web-Worker
Der wahre Nutzen von SharedArrayBuffer
zeigt sich bei der Verwendung in Kombination mit Web-Workern. Der folgende Code ist ein Beispiel für die Verwendung gemeinsamer Speicherbereiche zwischen dem Hauptthread und einem Worker.
Im Haupt-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]);
Auf der Worker-Seite (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 diesem Beispiel erstellt der Hauptthread einen gemeinsamen Puffer und übergibt ihn an den Worker. Der Worker kann auf diesen Puffer zugreifen, um Werte zu lesen und zu ändern. Auf diese Weise können Daten zwischen Threads geteilt werden, ohne dass sie kopiert werden müssen.
Bidirektionale Aktualisierungsbestätigung
Durch die Verwendung von SharedArrayBuffer
können sowohl der Hauptthread als auch die Worker auf denselben Speicher lesen und schreiben, wodurch eine bidirektionale Aktualisierungsbestätigung ermöglicht wird. Nachfolgend ein Beispiel, bei dem der Hauptthread einen Wert setzt, ein Worker diesen Wert ändert und der Hauptthread anschließend die Aktualisierung überprüft.
Im Haupt-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};
Auf der Worker-Seite (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 diesem Beispiel schreibt der Hauptthread zunächst den Wert
100
. Nachdem der Worker ihn gelesen hat, schreibt er ihn auf200
um. Danach benachrichtigt der Worker den Hauptthread, und dieser liest den gemeinsamen Speicher erneut aus, um die Aktualisierung zu bestätigen. Auf diese Weise ermöglicht die Kombination von Benachrichtigungen eine bidirektionale Aktualisierungsbestätigung.
Synchronisation mit Atomics
Beim Gebrauch von gemeinsam genutztem Speicher muss man vorsichtig sein, um Datenrennbedingungen und Inkonsistenzen zu vermeiden. Wenn mehrere Threads gleichzeitig auf denselben Speicher zugreifen, können Konflikte auftreten. Um dies zu verhindern, verwendet JavaScript das Atomics
-Objekt zur Synchronisierung.
Zum Beispiel kann man, um einen Zähler sicher mit mehreren Threads zu inkrementieren, Atomics
verwenden, um Konflikte zu vermeiden.
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
erhöht den Wert an einem bestimmten Index atomar und gibt den neuen Wert zurück. Diese Operation ist konfliktfrei mit anderen Threads garantiert. Atomics.load
wird auch verwendet, um Werte aus gemeinsam genutztem Speicher sicher zu lesen.
Warten und Benachrichtigen mit Atomics.wait
und Atomics.notify
Bei der Verwendung von SharedArrayBuffer
gibt es Situationen, in denen ein Worker warten muss, bis eine bestimmte Bedingung erfüllt ist, und sobald ein anderer Worker diese Bedingung erfüllt, muss er den wartenden Worker benachrichtigen. In solchen Fällen sind Atomics.wait
und Atomics.notify
hilfreich.
Atomics.wait
blockiert einen Thread, bis sich der Wert an einem bestimmten Index im gemeinsamen Speicher ändert, während Atomics.notify
wartende Threads benachrichtigt, dass sie fortfahren können. Dies ermöglicht ein sicheres Warten und Benachrichtigen zwischen mehreren Workern. Allerdings kann Atomics.wait
nicht im Hauptthread verwendet werden und ist nur innerhalb von Workern verfügbar.
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};
- Im Hauptthread wird ein
SharedArrayBuffer
als gemeinsamer Speicher erstellt und in einInt32Array
mit nur einem Element umgewandelt. Dieses einzelne Integer-Feld wird als Signal zur Synchronisation zwischen Workern verwendet. Als Nächstes werden zwei Worker erstellt und jeder erhält mittels der Eigenschaftname
eine Rolle:waiter
(die wartende Rolle) undnotifier
(die benachrichtigende Rolle). Abschließend wird der gemeinsame Speicherpuffer beiden Workern übergeben undonmessage
-Handler werden eingerichtet, sodass die von jedem Worker gesendeten Nachrichten empfangen werden können.
Auf der Worker-Seite (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 diesem Beispiel bleibt der
waiter
-Worker mitAtomics.wait
so lange im Wartezustand, wie der Wert an Index0
gleich0
ist. Andererseits, wenn dernotifier
-Worker den Wert mitAtomics.store
auf123
ändert und dannAtomics.notify
aufruft, wird derwaiter
-Worker fortgesetzt und kann den aktualisierten Wert abholen. Damit kann effizientes und sicheres Warten und Benachrichtigen zwischen Threads erreicht werden.
Verwendungszwecke für SharedArrayBuffer
SharedArrayBuffer
ist besonders nützlich für die folgenden Anwendungsfälle:.
- Echtzeitverarbeitung Es eignet sich für Anwendungen, die geringe Latenzzeiten erfordern, wie etwa Audio- und Videobearbeitung oder Game-Engines, bei denen Daten sofort zwischen Threads ausgetauscht werden müssen.
- Paralleles Rechnen
Bei der gleichzeitigen Verarbeitung großer Datenmengen mit mehreren Threads kann durch die Verwendung von
SharedArrayBuffer
das Kopieren von Speicher vermieden werden, was die Leistung verbessern kann. - Maschinelles Lernen Durch die Parallelisierung von Aufgaben wie Datenvorverarbeitung und Inferenz wird effiziente Berechnung möglich.
Sicherheitsüberlegungen
SharedArrayBuffer
ist eine leistungsstarke Funktion, birgt jedoch auch Sicherheitsrisiken. Insbesondere Bedenken bezüglich Seitenkanalangriffen wie Spectre
haben die Unterstützung vorübergehend gestoppt. Um diese Schwachstelle zu mildern, haben Browser die folgenden Maßnahmen umgesetzt:.
- Seitenisolation
Websites, die die Verwendung von
SharedArrayBuffer
zulassen, laufen in einem Prozess, der vollständig von anderen Websites isoliert ist. - Cross-Origin-Ressourcenrichtlinie
Um
SharedArrayBuffer
zu verwenden, müssen die HeaderCross-Origin-Opener-Policy
undCross-Origin-Embedder-Policy
korrekt gesetzt sein.
Zum Beispiel wird durch das Setzen der folgenden Header die Nutzung von SharedArrayBuffer
möglich:.
1Cross-Origin-Opener-Policy: same-origin
2Cross-Origin-Embedder-Policy: require-corp
Dies verhindert, dass externe Ressourcen den aktuellen Inhalt beeinträchtigen, und erhöht die Sicherheit.
Zusammenfassung
SharedArrayBuffer
ist ein sehr leistungsstarkes Werkzeug zum Teilen von Speicher zwischen mehreren Threads. Es ist eine wesentliche Technologie zur Leistungsverbesserung, und ihre Auswirkungen sind besonders in den Bereichen Echtzeitverarbeitung und paralleles Rechnen deutlich sichtbar. Allerdings birgt sie auch Sicherheitsrisiken, daher sind eine korrekte Konfiguration und Synchronisation wichtig.
Durch den Einsatz von SharedArrayBuffer
können Sie fortschrittlichere und leistungsfähigere Webanwendungen erstellen.
Sie können den obigen Artikel mit Visual Studio Code auf unserem YouTube-Kanal verfolgen. Bitte schauen Sie sich auch den YouTube-Kanal an.