Site icon Blog ARC Optimizer

Exploration de l'API Worker Thread dans Node


Les threads de travail ont surchargé les capacités de JavaScript (Node), car il n'était auparavant pas idéal pour les opérations gourmandes en ressources processeur. Cet article explore le fonctionnement des threads de travail dans Node.

Depuis sa conception, JavaScript est un langage à thread unique. Un seul thread dans le sens où un seul ensemble de commandes peut être exécuté à un moment donné dans le même processus. Par extension, Node.js n’est pas un bon choix pour mettre en œuvre des applications très gourmandes en ressources processeur. Pour résoudre ce problème, un concept expérimental de threads de travail a été introduit dans Node v10.5.0 et a été stabilisé dans la version 12 LTS.

Le module worker_threads permet l'utilisation de threads qui exécutent JavaScript en parallèle. Il est accessible avec l'une des syntaxes suivantes:

 const worker = require ('worker_threads');
// OU
import worker_threads;

Fonctionnement des threads de travail

Les threads de travail fonctionnent différemment du multi-threading traditionnel dans d'autres langages de haut niveau. La responsabilité d'un travailleur est d'exécuter un morceau de code fourni par le travailleur parent. Il fonctionne indépendamment des autres travailleurs, mais a la capacité de transmettre des informations entre lui et le travailleur parent.

Chaque travailleur est connecté à son travailleur parent via un canal de message . Le travailleur enfant peut écrire dans le canal de message à l'aide de la fonction parentPort.postMessage et le travailleur parent peut écrire dans le canal de message en appelant la fonction worker.postMessage () sur l'instance de travail.

Maintenant, on peut se demander comment un nœud worker fonctionne de manière indépendante, car JavaScript ne prend pas en charge la concurrence. La réponse: v8 Isolate . Un isolat v8 est une instance indépendante de l'exécution de Chrome v8, qui possède son propre tas JavaScript et une file d'attente de micro-tâches. Cela permet à chaque worker Node.js d'exécuter son code JavaScript de manière totalement isolée des autres nœuds de calcul. L’inconvénient est que les travailleurs ne peuvent pas accéder directement aux tas les uns des autres. Pour cette raison, chaque worker aura sa propre copie de la boucle d'événement libuv, qui est indépendante des boucles d'événement des autres workers et du worker parent.

Using Worker Threads

Comme indiqué précédemment, l'API worker_thread a été introduite dans v10.5.0 et stabilisé en v12. Cependant, si vous utilisez une version antérieure à la version 11.7.0, vous devez l'activer en utilisant l'indicateur - experimental-worker lors de l'appel de Node.js.

Dans notre exemple, nous sommes va implémenter le fichier principal, où nous allons créer un thread de travail et lui donner des données. L'API est basée sur les événements, mais elle sera intégrée dans une promesse qui se résout dans le premier message reçu du worker:

 // index.js
// exécuter avec node --experimental-worker index.js sur Node.js 10.x
const {Worker} = require ('worker_threads')

const runService = (workerData) => {
  retourner une nouvelle promesse ((résoudre, rejeter) => {
    const worker = new Worker ('./ myWorker.js', {workerData});
    worker.on ('message', résoudre);
    worker.on ('erreur', rejeter);
    worker.on ('sortie', (code) => {
      si (code! == 0)
        rejeter (nouvelle erreur (`Le travailleur s'est arrêté avec le code de sortie $ {code}`));
    })
  })
}

const run = async () => {
  résultat const = attendre runService ('monde')
  console.log (résultat);
}

run (). catch (err => console.error (err))

Comme vous pouvez le voir, c'est aussi simple que de passer le nom de fichier comme argument et les données que nous voulons que le travailleur traite. Notez que ces données sont clonées et ne se trouvent dans aucune mémoire partagée. Ensuite, nous attendons que le thread Worker nous envoie un message en écoutant l'événement message . Ensuite, nous devons implémenter le service.

 const {workerData, parentPort} = require ('worker_threads')

// Vous pouvez faire n'importe quel truc lourd ici, de manière synchrone
// sans bloquer le "thread principal"
parentPort.postMessage ({salutations: workerData})

Ici, nous avons besoin de deux choses. Premièrement, les workerData que l'application principale nous a envoyées, et deuxièmement, un moyen de renvoyer des informations à l'application principale. Ceci est fait avec le parentPort qui a une méthode postMessage où nous transmettrons le résultat de notre traitement.

C'est aussi simple que cela! C'est un exemple un peu simple, mais nous pouvons créer des choses plus complexes – par exemple, nous pourrions envoyer plusieurs messages à partir du thread de travail indiquant l'état d'exécution si nous avons besoin de fournir des commentaires, ou nous pouvons envoyer des résultats partiels. Imaginez que vous traitez des milliers d'images et que vous souhaitiez peut-être envoyer un message par image traitée, mais que vous ne voulez pas attendre qu'elles soient toutes traitées.

Tirer le meilleur parti des threads de travail

Comprendre à le moins les bases de leur fonctionnement nous aident en effet à obtenir les meilleures performances en utilisant les threads de travail. Lors de l'écriture d'applications plus complexes que notre exemple, nous devons nous souvenir des deux problèmes majeurs suivants concernant les threads de travail.

  1. Même si les threads de travail sont plus légers que les processus réels, les processus de création impliquent un travail sérieux et peuvent être coûteux s'ils sont effectués fréquemment.
  2. Il n'est pas rentable d'utiliser des threads de travail pour implémenter des opérations d'E / S parallèles, car l'utilisation des mécanismes d'E / S natifs de Node.js est bien plus rapide que de démarrer un thread de travail à partir de zéro juste pour faire cela. la première préoccupation, nous devons implémenter "Worker Thread Pooling".

    Worker Thread Pooling

    Un pool de threads de travail Node.js est un groupe de threads de travail en cours d'exécution qui sont disponibles pour être utilisés pour les tâches entrantes. Lorsqu'une nouvelle tâche arrive, elle peut être transmise à un travailleur disponible via le canal de message parent-enfant. Une fois que le worker a terminé la tâche, il peut transmettre les résultats au worker parent via le même canal de message.

    S'il est correctement implémenté, le regroupement de threads peut améliorer considérablement les performances car il réduit la surcharge supplémentaire de création de nouveaux threads. Il convient également de mentionner que la création d'un grand nombre de threads n'est pas non plus efficace, car le nombre de threads parallèles qui peuvent être exécutés efficacement est toujours limité par le matériel.

    Conclusion

    L'introduction des threads de travail a surchargé le capacités de JavaScript (Node), car ils ont effectivement pris en charge son plus grand défaut de ne pas être idéal pour les opérations gourmandes en CPU. Pour plus d'informations, consultez la documentation worker_threads .





Source link
Quitter la version mobile