SharedArrayBuffer in JavaScript

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 auf 200 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 ein Int32Array 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 Eigenschaft name eine Rolle: waiter (die wartende Rolle) und notifier (die benachrichtigende Rolle). Abschließend wird der gemeinsame Speicherpuffer beiden Workern übergeben und onmessage-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 mit Atomics.wait so lange im Wartezustand, wie der Wert an Index 0 gleich 0 ist. Andererseits, wenn der notifier-Worker den Wert mit Atomics.store auf 123 ändert und dann Atomics.notify aufruft, wird der waiter-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 Header Cross-Origin-Opener-Policy und Cross-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.

YouTube Video