SharedArrayBuffer en JavaScript

SharedArrayBuffer en JavaScript

Cet article explique le SharedArrayBuffer en JavaScript.

Nous fournirons une explication détaillée des bases de SharedArrayBuffer, comment l'utiliser, des cas d'utilisation spécifiques et des considérations de sécurité.

YouTube Video

SharedArrayBuffer en JavaScript

SharedArrayBuffer est un outil puissant en JavaScript pour partager la mémoire entre plusieurs threads. En particulier en combinaison avec les Web Workers, il permet un traitement parallèle, ce qui le rend efficace pour les tâches exigeantes en calcul et les applications nécessitant des capacités en temps réel.

Qu'est-ce que SharedArrayBuffer ?

SharedArrayBuffer fournit un tampon mémoire en JavaScript qui permet de partager des données binaires entre plusieurs threads (principalement des Web Workers). Un ArrayBuffer classique nécessite une copie entre le thread principal et les workers, mais SharedArrayBuffer permet le partage direct de mémoire sans copie, améliorant ainsi considérablement les performances.

Caractéristiques

  • Mémoire partagée Cela permet à plusieurs threads de travailler sur le même espace mémoire.
  • Amélioration des performances Étant donné que la copie peut être omise, la surcharge est réduite lors du traitement de grandes quantités de données.
  • Synchronisation des threads Vous pouvez l'utiliser avec Atomics pour effectuer une synchronisation afin d'éviter les conflits lors de l'accès à la mémoire.

Exemple d'utilisation de base

 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

Dans cet exemple, nous créons une zone mémoire de 16 octets à l'aide de SharedArrayBuffer et traitons cette zone mémoire comme un Int32Array. Ce tampon mémoire peut être partagé entre plusieurs threads.

Utilisation avec les Web Workers

La véritable valeur de SharedArrayBuffer se manifeste lorsqu'il est utilisé avec des Web Workers. Le code suivant est un exemple d'utilisation de mémoire partagée entre le thread principal et un worker.

Sur le thread principal

 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]);

Côté 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};
  • Dans cet exemple, le thread principal crée un tampon partagé et le transmet au worker. Le worker peut accéder à ce tampon pour lire et modifier des valeurs. De cette manière, les données peuvent être partagées entre threads sans copie.

Confirmation de mise à jour bidirectionnelle

En utilisant SharedArrayBuffer, le thread principal et les workers peuvent lire et écrire dans la même mémoire, ce qui permet une confirmation de mise à jour bidirectionnelle. Ci-dessous un exemple où le thread principal définit une valeur, un worker modifie cette valeur, puis le thread principal vérifie la mise à jour.

Sur le thread principal

 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};

Côté 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};
  • Dans cet exemple, le thread principal écrit d'abord la valeur 100, puis après que le worker l'a lue, il la réécrit à 200. Après cela, le worker notifie le thread principal, qui relit alors la mémoire partagée pour confirmer la mise à jour. De cette manière, la combinaison des notifications permet une confirmation de mise à jour bidirectionnelle.

Synchronisation avec Atomics

Lors de l'utilisation de la mémoire partagée, il faut veiller aux conditions de concurrence des données et aux incohérences. Lorsque plusieurs threads accèdent simultanément à la même mémoire, des conflits peuvent survenir. Pour éviter cela, JavaScript utilise l'objet Atomics pour la synchronisation.

Par exemple, pour incrémenter un compteur en toute sécurité avec plusieurs threads, vous pouvez utiliser Atomics pour prévenir les conflits.

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 incrémente la valeur à un index spécifique de manière atomique et renvoie la nouvelle valeur. Cette opération garantit l'absence de conflits avec d'autres threads. Atomics.load est également utilisé pour lire des valeurs en toute sécurité depuis la mémoire partagée.

Attente et notification avec Atomics.wait et Atomics.notify

Lors de l'utilisation de SharedArrayBuffer, il existe des situations où un worker doit attendre qu'une certaine condition soit remplie, et un autre worker doit ensuite notifier le worker en attente une fois la condition remplie. Dans de tels cas, Atomics.wait et Atomics.notify sont utiles.

Atomics.wait bloque un thread jusqu'à ce que la valeur à un index précis dans la mémoire partagée soit modifiée, tandis que Atomics.notify informe les threads en attente qu'ils peuvent continuer. Cela permet une attente et une notification sécurisées entre plusieurs workers. Cependant, Atomics.wait ne peut pas être utilisé sur le fil principal et n'est disponible que dans les 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};
  • Sur le fil principal, un SharedArrayBuffer est créé comme mémoire partagée et converti en un Int32Array ne contenant qu'un seul élément. Cet emplacement entier unique est utilisé comme signal de synchronisation entre les workers. Ensuite, deux workers sont créés et un rôle est attribué à chacun via la propriété name : waiter (celui qui attend) et notifier (celui qui signale). Enfin, le buffer partagé est transmis aux deux workers et les gestionnaires onmessage sont configurés pour que les messages envoyés par chaque worker puissent être reçus.

Côté 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!
  • Dans cet exemple, le worker waiter reste en état d’attente grâce à Atomics.wait tant que la valeur à l’index 0 est 0. D’un autre côté, lorsque le worker notifier change la valeur en 123 via Atomics.store et appelle Atomics.notify, le worker waiter reprend et peut obtenir la valeur mise à jour. Avec cela, une attente et une notification efficaces et sûres entre les threads peuvent être réalisées.

Cas d'utilisation pour SharedArrayBuffer

SharedArrayBuffer est particulièrement utile pour les cas d'utilisation suivants :.

  • Traitement en temps réel Il convient aux applications nécessitant une faible latence, telles que le traitement audio et vidéo ou les moteurs de jeux, où les données doivent être partagées instantanément entre les threads.
  • Calcul parallèle Lors du traitement simultané de grandes quantités de données avec plusieurs threads, l'utilisation de SharedArrayBuffer évite la copie de mémoire et peut améliorer les performances.
  • Apprentissage automatique En parallélisant des tâches comme la prétraitement des données et l'inférence, des calculs efficaces deviennent possibles.

Considérations de sécurité

SharedArrayBuffer est une fonctionnalité puissante, mais elle comporte également des risques de sécurité. En particulier, les préoccupations concernant les attaques par canaux auxiliaires comme Spectre ont temporairement suspendu son support. Pour atténuer cette vulnérabilité, les navigateurs ont mis en œuvre les mesures suivantes :.

  • Isolement de site Les sites qui autorisent l'utilisation de SharedArrayBuffer s'exécuteront dans un processus complètement isolé des autres sites.
  • Politique de ressource cross-origin Pour utiliser SharedArrayBuffer, les en-têtes Cross-Origin-Opener-Policy et Cross-Origin-Embedder-Policy doivent être correctement définis.

Par exemple, en configurant des en-têtes comme ceux-ci, l'utilisation de SharedArrayBuffer devient possible :.

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

Cela empêche les ressources externes d'interférer avec le contenu actuel et augmente la sécurité.

Résumé

SharedArrayBuffer est un outil très puissant pour le partage de mémoire entre plusieurs threads. C'est une technologie essentielle pour améliorer les performances, et ses effets sont particulièrement évidents dans les domaines du traitement en temps réel et du calcul parallèle. Cependant, elle implique également des risques de sécurité, donc une configuration et une synchronisation correctes sont importantes.

En utilisant SharedArrayBuffer, vous pouvez concevoir des applications web plus avancées et performantes.

Vous pouvez suivre l'article ci-dessus avec Visual Studio Code sur notre chaîne YouTube. Veuillez également consulter la chaîne YouTube.

YouTube Video