in

Introduction au JavaScript multithread | InfoWorld

Le langage JavaScript est l'une des merveilles du monde du logiciel. Il est incroyablement puissant, flexible et polyvalent. Cependant, sa conception fondamentale présente une limite : elle est monothread. Le JavaScript traditionnel semble gérer des tâches parallèles, mais il s'agit d'une astuce de syntaxe. Pour obtenir un véritable parallélisme, vous devez utiliser des approches multithread modernes telles que les web workers et les threads de travail.

Parallélisme vs. concurrence

La façon la plus simple de comprendre la différence entre le parallélisme et la concurrence est de dire que la concurrence est sémantique tandis que le parallélisme est une implémentation. Ce que je veux dire, c'est que la concurrence vous permet dire au système (sémantique) faire plusieurs choses à la fois. Le parallélisme consiste simplement à effectuer plusieurs tâches simultanément (implémentation). Tout traitement parallèle est concurrent, mais toute programmation concurrente n'est pas parallèle.

En JavaScript vanilla, vous pouvez demander à la plateforme de faire plusieurs choses :


function fetchPerson(id) {
  return new Promise((resolve, reject) => {
    fetch(`https://swapi.dev/api/people/${id}`)
      .then(response => response.json())
      .then(data => resolve(data))
      .catch(error => reject(error));
  });
}

const lukeId = 1;
const leiaId = 5;

console.log("Fetching Star Wars characters...");

// Fetch character data concurrently (non-blocking)
Promise.all((fetchPerson(lukeId), fetchPerson(leiaId)))
  .then(data => {
    console.log("Characters received:");
    console.log(data(0)); // Data for Luke Skywalker (ID: 1)
    console.log(data(1)); // Data for Leia Organa (ID: 5)
  })
  .catch(error => console.error("Error fetching characters:", error));

console.log("Moving on to other things...");

// Fetching Star Wars characters...
// Moving on to other things...

Characters received:
{name: 'Luke Skywalker', height: '172', mass: '77', …}
{name: 'Leia Organa', height: '150', mass: '49', …}

Cela semble récupérer des données sur Luke et Leia en même temps, en utilisant Promise.all pour exécuter deux fetch appelle ensemble. En réalité, JavaScript planifiera chaque tâche pour qu'elle soit gérée par un seul thread d'application.

Cela est dû au fait que JavaScript utilise une boucle d'événements. La boucle récupère les éléments d'une file d'attente si rapidement qu'ils semblent souvent se produire simultanément, mais il ne s'agit pas d'un processus véritablement simultané.

À vraiment Pour faire deux choses à la fois, nous avons besoin de plusieurs threads. Les threads sont une abstraction des processus du système d'exploitation sous-jacent et de leur accès au matériel, y compris les processeurs multicœurs.

Multithreading avec les travailleurs Web

Les Web Workers vous permettent de générer des threads dans un navigateur Web. Vous pouvez simplement charger un script de travail distinct du script principal, et il gérera les messages asynchrones. Chaque gestionnaire de messages est exécuté dans son propre thread, ce qui vous offre un véritable parallélisme.

Pour notre exemple simple d'API Star Wars, nous souhaitons générer des threads qui géreront ou récupéreront les requêtes. Utiliser des web workers pour cela est évidemment excessif, mais cela permet de simplifier les choses. Nous voulons créer un web worker qui acceptera un message du thread principal et émettra les requêtes.

Voici ce que dit notre script principal (main.js) ressemble à maintenant :


function fetchPersonWithWorker(id) {
  return new Promise((resolve, reject) => {
    const worker = new Worker('worker.js');

    worker.onmessage = function(event) {
      if (event.data.error) {
        reject(event.data.error);
      } else {
        resolve(event.data);
      }
      worker.terminate(); // Clean up the worker after receiving the data
    }

    worker.postMessage({ url: `https://swapi.dev/api/people/${id}` });
  });
}

const lukeId = 1; const leiaId = 5;

console.log("Fetching Star Wars characters with web worker...");

// Fetch character data concurrently (truly parallel)
Promise.all((fetchPersonWithWorker(lukeId), fetchPersonWithWorker(leiaId)))
  .then(data => {
    console.log("Characters received:");
    console.log(data(0)); // Data for Luke Skywalker (ID: 1)
    console.log(data(1)); // Data for Leia Organa (ID: 5)
  })
  .catch(error => console.error("Error fetching characters:", error));

console.log("Moving on to other things...");

Ceci est similaire au premier exemple, mais au lieu d'utiliser une fonction qui fonctionne localement dans Promise.allnous passons dans le fetchPersonWithWorker fonction. Cette dernière fonction crée une Worker objet appelé workerqui est configuré avec le worker.js déposer.

Une fois l'objet travailleur créé, nous fournissons un onmessage événement sur celui-ci. Nous allons l'utiliser pour gérer les messages renvoyés par le travailleur. Dans notre cas, nous résolvons ou rejetons la promesse que nous renvoyons (consommée par Promise.all dans le script principal), puis nous terminons le travailleur.

Après cela, nous appelons worker.postMessage() et transmettez un objet JSON simple avec un champ URL défini sur l'URL que nous voulons appeler.

Le travailleur du Web

Voici l’autre côté de l’équation, en worker.js:


// worker.js
onmessage = function(event) {
  console.log(“onmessage: “ + event.data); //  {"url":"https://swapi.dev/api/people/1"}
  const { url } = event.data;
  fetch(url)
    .then(response => response.json())
    .then(data => postMessage(data))
    .catch(error => postMessage({ error }));
}

Notre simplicité onmessage le gestionnaire accepte l'événement et utilise le champ URL pour émettre les mêmes appels de récupération que précédemment, mais cette fois nous utilisons postMessage() pour communiquer les résultats à main.js.

Donc, vous pouvez voir que nous communiquons entre les deux mondes avec des messages en utilisant postMessage et onmessage. Se souvenir du onmessage les gestionnaires du worker se produisent de manière asynchrone dans leurs propres threads. (N'utilisez pas de variables locales pour stocker des données : elles risquent d'être effacées).

Threading côté serveur avec threads de travail

Examinons maintenant le côté serveur, en utilisant Node.js. Dans ce cas, au lieu de travailleurs Web, nous utilisons le concept de fil de travailUn thread de travail est similaire à un travailleur Web dans le sens où nous transmettons des messages dans les deux sens du thread principal au travailleur.

Par exemple, disons que nous avons deux fichiers, main.js et worker.jsNous courrons main.js (en utilisant la commande : node main.js) et il générera un thread en chargeant worker.js comme un thread de travail. Voici notre main.js déposer:


const { Worker } = require('worker_threads');

function fetchPersonWithWorker(id) {
  return new Promise((resolve, reject) => {
    const worker = new Worker('./worker.js', { workerData: id });

    worker.on('message', (data) => {
      if (data.error) {
        reject(data.error);
      } else {
        resolve(data);
      }
      worker.terminate();
    });

    worker.on('error', (error) => reject(error));

    let url = `https://swapi.dev/api/people/${id}`;
    worker.postMessage({ url });
  });
}

const lukeId = 1;
const leiaId = 5;

console.log("Fetching Star Wars characters with worker threads...");

Promise.all((fetchPersonWithWorker(lukeId), fetchPersonWithWorker(leiaId)))
  .then(data => {
    console.log("Characters received: "+ JSON.stringify(data) );
    console.log(data(0)); // Data for Luke Skywalker (ID: 1)
    console.log(data(1)); // Data for Leia Organa (ID: 5)
  })
  .catch(error => console.error("Error fetching characters:", error));

console.log("Moving on to other things...");

Nous importons Worker du worker_threads module, mais notez qu'il est intégré à Node, nous n'avons donc pas besoin de NPM pour cela. Pour lancer le travailleur, nous créons un nouveau Worker objet et lui donner le worker.js fichier comme paramètre. Une fois cela fait, nous ajoutons un écouteur de messages qui résout ou rejette notre promesse, c'est exactement comme nous l'avons fait pour le travailleur Web. Nous terminons également le travailleur une fois terminé, pour nettoyer les ressources.

Enfin, nous envoyons au travailleur un nouveau message contenant l’URL que nous souhaitons récupérer.

Le fil de travail

Voici un aperçu de worker.js :


const { parentPort } = require('worker_threads');

parentPort.on('message', (msg) => {
        console.log("message(worker): " + msg.url);

  fetch(msg.url)
    .then(response => response.json())
    .then(data => parentPort.postMessage(data))
    .catch(error => parentPort.postMessage({ error }));
});

Encore une fois, nous importons de worker_threadscette fois le parentPort objet. Il s'agit d'un objet qui nous permet de communiquer avec le thread principal. Dans notre cas, nous écoutons l'événement message, et lorsqu'il est reçu, nous décompressons le champ url et l'utilisons pour émettre une requête.

De cette façon, nous avons obtenu des requêtes réellement simultanées aux URL. Si vous exécutez l'exemple avec node main.jsvous verrez les données des deux URL affichées sur la console.

Conclusion

Vous avez vu les mécanismes fondamentaux permettant d'obtenir des threads véritablement parallèles en JavaScript, à la fois dans le navigateur et sur le serveur. La manière dont ils sont exécutés dépend du système d'exploitation et du profil matériel de l'environnement hôte réel, mais en général, ils vous donnent accès à des processus multithreads.

Bien que JavaScript ne prenne pas en charge la portée et la profondeur de la programmation simultanée trouvées dans un langage comme Java, les travailleurs Web et les threads de travail vous offrent le mécanisme de base du parallélisme lorsque vous en avez besoin.

Vous pouvez trouver les exemples exécutables de cet article sur GitHub ici. Pour exécuter l'exemple de Web Worker, saisissez


$ node server.js 

depuis le répertoire racine.

Pour exécuter l'exemple de thread de travail, tapez :


~/worker-thread $ node main.js

Droits d'auteur © 2024 IDG Communications, Inc.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

GIPHY App Key not set. Please check settings

    Info Tv : Parfait Ayissi décapsule Maahlox : « Un vrai artiste ne vend pas la bière »

    elle affiche son corps de rêve à la piscine