One place for hosting & domains

      processus

      Comment utiliser ps, kill et nice pour gérer des processus sous Linux


      Introduction


      Un serveur Linux, tout comme tous les autres ordinateurs que vous connaissez, exécute des applications. L’ordinateur considère cela comme des « processus ».

      Bien que Linux se chargera de la gestion de bas niveau et en coulisses du cycle de vie d’un processus, vous avez besoin de pouvoir interagir avec le système d’exploitation et à le gérer ainsi à un niveau supérieur.

      Dans ce guide, nous allons voir certains des aspects simples de la gestion de processus. Linux vous propose un vaste ensemble d’outils.

      Nous allons voir ces idées sur un Ubuntu 12.04 VPS. Cependant, toute distribution Linux moderne fonctionnera de manière similaire.


      top


      Afin de déterminer quels sont les processus en cours d’exécution sur votre serveur, la façon la plus simple consiste à exécuter la commande top :

      top***
      
      top - 15:14:40 up 46 min, 1 user, load average: 0.00, 0.01, 0.05 Tasks:  56 total,   1 running, 55 sleeping, 0 stopped, 0 zombie Cpu(s): 0.0%us, 0.0%sy,  0.0%ni,100.0%id, 0.0%wa,  0.0%hi,  0.0%si, 0.0%st Mem:   1019600k total,   316576k used,   703024k free, 7652k buffers Swap:        0k total,        0k used,        0k free,   258976k cached   PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND            1 root      20 0 24188 2120 1300 0.0 0.2   0:00.56 init     2 root 20   0 0 0 0 S 0 0.0 0:00.00 kthreadd     3 root      20 0 0    0 0 S 0.0 0.0 0:00.07 ksoftirqd/0     6 root RT 0 0    0 S  0.0  0.0   0:00.00 migration/0            7 root      RT   0     0 0    0 S  0.0  0.0   0:00.03 watchdog/0             8 root       0 -20     0    0    0 S  0.0  0.0   0:00.00 cpuset                 9 root       0 -20     0    0    0 S  0.0  0.0   0:00.00 khelper               10 root      20   0     0    0    0 S  0.0  0.0   0:00.00 kdevtmpfs
      

      Le premier morceau d’information vous donne les statistiques sur les systèmes, comme la charge du système et le nombre total de tâches.

      Vous pouvez facilement voir qu’il y a 1 processus en cours d’exécution et que 55 processus sont dormants (aka idle/n’utilisant pas les ressources du processeur).

      La partie inférieure comporte les processus en cours d’exécution et leurs statistiques d’utilisation.

      htop


      Vous disposez d’une version améliorée de top, appelée htop, dans les référentiels. Installez-la avec cette commande :

      sudo apt-get install htop
      

      Si nous exécutons la commande htop, l’affichage qui apparaîtra sera plus convivial :

      htop***
      
        Mem[|||||||||||           49/995MB]     Load average: 0.00 0.03 0.05   CPU[                          0.0%]     Tasks: 21, 3 thr; 1 running   Swp[                         0/0 Mo]     Uptime: 00:58:11   PID USER      PRI  NI  VIRT   RES   SHR S CPU% MEM%   TIME+  Command  1259 root       20   0 25660  1880  1368 R  0.0  0.2  0:00.06 htop     1 root       20   0 24188  2120  1300 S  0.0  0.2  0:00.56 /sbin/init   311 root       20   0 17224   636   440 S  0.0  0.1  0:00.07 upstart-udev-brid   314 root       20   0 21592  1280   760 S  0.0  0.1  0:00.06 /sbin/udevd --dae   389 messagebu  20   0 23808   688   444 S  0.0  0.1  0:00.01 dbus-daemon --sys   407 syslog     20   0  243M  1404  1080 S  0.0  0.1  0:00.02 rsyslogd -c5   408 syslog     20   0  243M  1404  1080 S  0.0  0.1  0:00.00 rsyslogd -c5   409 syslog     20   0  243M  1404  1080 S  0.0  0.1  0:00.00 rsyslogd -c5   406 syslog     20   0  243M  1404  1080 S  0.0  0.1  0:00.04 rsyslogd -c5   553 root       20   0 15180   400   204 S  0.0  0.0  0:00.01 upstart-socket-br
      

      Vous pouvez en savoir plus sur la manière d’utiliser top et htop ici.


      top et htop vous offrent tous les deux une belle interface pour visualiser les processus en cours d’exécution, similaire à un gestionnaire de tâches graphique.

      Cependant, ces outils ne sont pas toujours suffisamment flexibles pour couvrir tous les scénarios correctement. Une commande puissante appelée ps est souvent utilisée pour répondre à ces problèmes.

      Lorsque vous appelez une commande sans arguments, la sortie peut manquer d’éclat :

      ps***
      
        PID TTY          TIME CMD  1017 pts/0    00:00:00 bash  1262 pts/0    00:00:00 ps
      

      Cette sortie affiche tous les processus associés à l’utilisateur et la session du terminal actuels. Cela est logique, car nous exécutons actuellement bash et ps avec ce terminal.

      Pour obtenir une image plus complète des processus sur ce système, nous pouvons exécuter ce qui suit :

      ps aux***
      
      USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND root         1  0.0  0.2  24188  2120 ?        Ss   14:28   0:00 /sbin/init root         2  0.0  0.0      0     0 ?        S    14:28   0:00 [kthreadd] root         3  0.0  0.0      0     0 ?        S    14:28   0:00 [ksoftirqd/0] root         6  0.0  0.0      0     0 ?        S    14:28   0:00 [migration/0] root         7  0.0  0.0      0     0 ?        S    14:28   0:00 [watchdog/0] root         8  0.0  0.0      0     0 ?        S<   14:28   0:00 [cpuset] root         9  0.0  0.0      0     0 ?        S<   14:28   0:00 [khelper] . . .
      

      Ces options indiquent à ps d’afficher les processus détenus par tous les utilisateurs (quel que soit le terminal auquel ils sont associés) sous un format convivial pour les utilisateurs.

      Pour avoir une vue en arborescencee, dans laquelle les relations hiérarchiques sont illustrées, nous pouvons exécuter la commande avec les options suivantes :

      ps axjf***
      
       PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND     0     2     0     0 ?           -1 S        0   0:00 [kthreadd]     2     3     0     0 ?           -1 S        0   0:00  _ [ksoftirqd/0]     2     6     0     0 ?           -1 S        0   0:00  _ [migration/0]     2     7     0     0 ?           -1 S        0   0:00  _ [watchdog/0]     2     8     0     0 ?           -1 S<       0   0:00  _ [cpuset]     2     9     0     0 ?           -1 S<       0   0:00  _ [khelper]     2    10     0     0 ?           -1 S        0   0:00  _ [kdevtmpfs]     2    11     0     0 ?           -1 S<       0   0:00  _ [netns] . . .
      

      Comme vous pouvez le voir, le processus kthreadd est montré comme un parent du processus ksoftirqd/0 et les autres.

      Une remarque sur les identifiants de processus


      Dans les systèmes de type Linux et Unix, chaque processus se voit attribuer un process ID ou** PID**. Voici de quelle manière le système d’exploitation identifie et assure le suivi des processus.

      Vous pouvez rapidement obtenir le PID d’un processus en utilisant la commande pgrep :

      pgrep bash***
      
      1017
      

      Cette commande interrogera simplement l’ID du processus et la renverra.

      Le premier processus généré au démarrage, appelé init, se voit attribuer le PID de « 1 ».

      pgrep init***
      
      1
      

      Ce processus est ensuite chargé de générer tout autre processus sur le système. Les processus suivants se voient attribuer des numéros PID plus grands.

      Le parent d’un processus est le processus qui était chargé de le générer. Les processus parent disposent d’un PPID, que vous pouvez voir dans l’en-tête des colonnes dans de nombreuses applications de gestion de processus, dont top, htop et ps.

      Toute communication sur les processus entre l’utilisateur et le système d’exploitation implique, à un moment donné de l’opération, la traduction entre les noms de processus et le PID. C’est pour cette raison que les utilitaires vous indiquent le PID.

      Relations parent-enfant


      La création d’un processus enfant se fait en deux étapes : fork(), qui crée un nouvel espace d’adresse et copie les ressources que le parent possède via une copie-sur-écriture pour les rendre disponibles sur le processus enfant ; et exec(), qui charge un exécutable dans l’espace de l’adresse et l’exécute.

      Dans le cas où un processus enfant meurt avant son parent, l’enfant devient un zombie jusqu’à que le parent collecte les informations le concernant ou indique au noyau qu’il n’a pas besoin de ces informations Les ressources du processus enfant seront ensuite libérées. Cependant, si le processus parent meurt avant l’enfant, l’enfant sera adopté par l’init, bien qu’il puisse également être réattribué à un autre processus.


      Tous les processus de Linux répondent à des signals. Les signaux permettent d’indiquer aux programmes, au niveau du système d’exploitation, de s’arrêter ou de modifier leur comportement.


      La façon la plus courante de transmettre des signaux à un programme consiste à utiliser la commande kill.

      Comme vous pouvez vous y attendre, la fonctionnalité par défaut de cet utilitaire consiste à tenter de tuer un processus :

      <pre>kill <span class=“highlight”>PIDoftarget_process</span></pre>

      Cela envoie le signal TERM au processus. Le signal TERM indique au processus de bien vouloir se terminer. Cela permet au programme d’effectuer des opérations de nettoyage et de s’arrêter en douceur.

      Si le programme se comporte mal et ne se ferme pas lorsque le signal TERM est actionné, nous pouvons escalader le signal en passant le signal KILL :

      <pre>kill -KILL <span class=“highlight”>PIDoftarget_process</span></pre>

      Il s’agit d’un signal spécial que n’est pas envoyé au programme.

      Au lieu de cela, il est envoyé au noyau du système d’exploitation qui interrompt le processus. Vous pouvez l’utiliser pour contourner les programmes qui ignorent les signaux qui leur sont envoyés.

      Chaque signal est associé à un numéro que vous pouvez passer à la place du nom. Par exemple, vous pouvez passer « -15 » au lieu de « -TERM » et « -9 » au lieu de « -KILL ».


      Les signaux ne servent pas uniquement à fermer des programmes. Vous pouvez également les utiliser pour effectuer d’autres actions.

      Par exemple, de nombreux démons redémarrent lorsqu’un signal HUP ou de suspension leur est envoyé Apache est un programme qui fonctionne ainsi.

      <pre>sudo kill -HUP <span class=“highlight”>pidofapache</span></pre>

      La commande ci-dessus poussera Apache à recharger son fichier de configuration et à reprendre le contenu d’utilisation.

      Vous pouvez répertorier tous les signaux que vous pouvez envoyer avec kill en saisissant :

      kill -l***
      
      1) SIGHUP    2) SIGINT   3) SIGQUIT  4) SIGILL   5) SIGTRAP  6) SIGABRT  7) SIGBUS   8) SIGFPE   9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM . . .
      

      Bien que la façon classique d’envoyer des signaux consiste à utiliser des PIDS, il existe également des méthodes de le faire avec des noms de processus réguliers.

      La commande pkill fonctionne de manière pratiquement de la même manière que kill, mais elle fonctionne plutôt avec le nom d’un processus :

      pkill -9 ping
      

      La commande ci-dessus est l’équivalent de :

      kill -9 `pgrep ping`
      

      Vous pouvez utiliser la commande killall pour envoyer un signal à chaque instance d’un processus donné :

      killall firefox
      

      La commande ci-dessus enverra le signal TERM à chaque instance de firefox en cours d’exécution sur l’ordinateur.


      Il vous arrivera souvent de vouloir ajuster la priorité donnée aux processus dans un environnement de serveur.

      Certains processus peuvent être considérés comme critiques à la mission pour votre situation, tandis que d’autres peuvent être exécutés chaque fois qu’il y aura des ressources restantes.

      Linux contrôle la priorité par le biais d’une valeur appelée niceness.

      Les tâches hautement prioritaires sont considérées comme moins nice, car elles ne partagent pas également les ressources. Les processus faiblement prioritaires sont en revanche nice car ils insistent à prendre seulement des ressources minimales.

      Lorsque nous avons exécuté top au début de l’article, il y avait une colonne nommée « NI ». Il s’agit de la valeur nice du processus :

      top***
      
       Tasks:  56 total,   1 running,  55 sleeping,   0 stopped,   0 zombie Cpu(s):  0.0%us,  0.3%sy,  0.0%ni, 99.7%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st Mem:   1019600k total,   324496k used,   695104k free,     8512k buffers Swap:        0k total,        0k used,        0k free,   264812k cached   PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND            1635 root      20   0 17300 1200  920 R  0.3  0.1   0:00.01 top                    1 root      20   0 24188 2120 1300 S  0.0  0.2   0:00.56 init                   2 root      20   0     0    0    0 S  0.0  0.0   0:00.00 kthreadd               3 root      20   0     0    0    0 S  0.0  0.0   0:00.11 ksoftirqd/0
      

      Les valeurs nice peuvent aller de « -19/-20  » (priorité la plus grande) à «19/20» (priorité la plus faible) en fonction du système.

      Pour exécuter un programme avec une certaine nice valeur, nous pouvons utiliser la commande nice :

      <pre>nice -n 15 <span class=“highlight”>commandtoexecute</span></pre>

      Elle fonctionne uniquement au démarrage d’un nouveau programme.

      Pour modifier la valeur nice d’un programme déjà en cours d’exécution, nous utilisons un outil appelé renice :

      <pre>renice 0 <span class=“highlight”>PIDtoprioritize</span></pre>

      Remarque : bien que nice fonctionne avec un nom de commande par nécessité, renice fonctionne en appelant le PID de processus

      Conclusion


      La gestion de processus est un sujet parfois difficile à comprendre pour les nouveaux utilisateurs car les outils utilisés sont différents de leurs homologues graphiques.

      Cependant, les idées sont familières et intuitives et deviennent naturelles avec un peu de pratique. Étant donné que les processus sont impliqués dans toutes les tâches que vous effectuez avec un système informatique, il est essentiel que vous appreniez à les contrôler efficacement.

      <div class=“author”>Par Justin Ellingwood</div>



      Source link

      Comment lancer des processus enfants dans Node.js


      L’auteur a choisi le COVID-19 Relief Fund pour recevoir un don dans le cadre du programme Write for DOnations.

      Introduction

      Lorsqu’un utilisateur lance un programme Node.js, ce dernier s’exécute comme un seul système d’exploitation (OS) *qui représente l’instance du programme en cours d’exécution. Dans le cadre de ce processus, Node.js exécute des programmes sur un seul thread. Comme mentionné plus haut dans cette série avec le tutoriel Comment écrire du code asynchrone dans Node.js, car un seul thread peut fonctionner sur un seul processus, les opérations qui prennent du temps à s’exécuter dans JavaScript peuvent bloquer le thread Node.js et retarder l’exécution d’un autre code. Une stratégie clé pour contourner ce problème consiste à lancer un *processus enfant, ou un processus créé par un autre processus, lorsqu’il est confronté à des tâches de longue haleine. Lorsqu’un nouveau processus est lancé, le système d’exploitation peut utiliser des techniques de multitraitement pour s’assurer que le processus principal de Node.js et le processus enfant supplémentaire s’exécutent simultanément ou en même temps.

      Node.js comprend le module child_process qui dispose de fonctions pour créer de nouveaux processus. En plus de traiter les tâches de longue haleine, ce module peut également s’interfacer avec l’OS et exécuter des commandes shell. Les administrateurs système peuvent utiliser Node.js pour exécuter les commandes shell pour structurer et maintenir leurs opérations en tant que module Node.js au lieu de scripts shell.

      Dans ce tutoriel, vous allez créer des processus enfants tout en exécutant une série d’exemples d’applications Node.js. Vous allez créer des processus avec le module child_process en récupérant les résultats d’un processus enfant via un buffer ou une chaîne avec la fonction exec(), puis à partir d’un flux de données avec la fonction spawn(). Vous terminerez en utilisant fork() pour créer un processus enfant d’un autre programme Node.js avec lequel vous pouvez communiquer au moment où il s’exécute. Pour illustrer ces concepts, vous allez écrire un programme pour lister le contenu d’un répertoire, un programme pour trouver des fichiers et un serveur web avec plusieurs terminaux.

      Conditions préalables

      Étape 1 – Création d’un processus enfant avec exec()

      Les développeurs créent généralement des processus enfants pour exécuter des commandes sur leur système d’exploitation lorsqu’ils ont besoin de manipuler la sortie de leurs programmes Node.js avec un shell, comme en utilisant le piping shell ou la redirection. La fonction exec() dans Node.js crée un nouveau processus shell et exécute une commande dans ce shell. La sortie de la commande est maintenue dans un buffer en mémoire, que vous pouvez accepter via une fonction de rappel passée dans exec().

      Commençons à créer nos premiers processus enfants dans Node.js. Tout d’abord, nous devons créer notre environnement de codage pour stocker les scripts que nous allons créer tout au long de ce tutoriel. Dans le terminal, créez un dossier appelé child-processes :

      Entrez ce dossier dans le terminal avec la commande cd :

      Créez un nouveau fichier appelé listFiles.js et ouvrez le fichier dans un éditeur de texte. Dans ce tutoriel, nous utiliserons nano, un éditeur de texte de terminal :

      Nous allons écrire un module Node.js qui utilise la fonction exec() pour exécuter la commande ls. La commande ls liste les fichiers et les dossiers dans un répertoire. Ce programme prend la sortie de la commande ls et l’affiche à l’utilisateur.

      Dans l’éditeur de texte, ajoutez le code suivant :

      ~/child-processes/listFiles.js

      const { exec } = require('child_process');
      
      exec('ls -lh', (error, stdout, stderr) => {
        if (error) {
          console.error(`error: ${error.message}`);
          return;
        }
      
        if (stderr) {
          console.error(`stderr: ${stderr}`);
          return;
        }
      
        console.log(`stdout:n${stdout}`);
      });
      

      Nous importons d’abord la commande exec() du module child_process, en utilisant la déstructuration JavaScript. Une fois importé, nous utilisons la fonction exec(). Le premier argument est la commande que nous aimerions exécuter. Dans ce cas, il s’agit de ls -lh, qui liste tous les fichiers et les dossiers dans le répertoire actuel en format long, avec une taille totale de fichier en unités lisibles par l’homme en tête de la sortie.

      Le deuxième argument est une fonction de rappel avec trois paramètres : error, stdout, et stderr. Si la commande ne s’exécute pas, error indiquera la raison de l’échec. Cela peut se produire si le shell ne peut pas trouver la commande que vous essayez d’exécuter. Si la commande est exécutée avec succès, toute donnée qu’elle écrit dans le flux de sortie standard est capturée dans stdout, et toute donnée qu’elle écrit dans le flux d’erreurs standard est capturée dans stderr.

      Remarque : il est important de garder en tête la différence entre error et stderr. Si la commande elle-même ne s’exécute pas, error capturera l’erreur. Si la commande s’exécute mais renvoie la sortie dans le flux d’erreur, stderr la capturera. Les programmes Node.js les plus résilients traiteront toutes les sorties possibles d’un processus enfant.

      Dans notre fonction de rappel, nous vérifions d’abord si nous avons reçu une erreur. Si c’est le cas, nous affichons le message de l’erreur (une propriété de l’objet Error) avec console.error() et terminons la fonction avec return. Nous vérifions alors si la commande a imprimé un message d’erreur et return si c’est le cas. Si la commande s’exécute avec succès, nous enregistrons la sortie de la console avec console.log().

      Exécutons ce fichier pour le voir en action. Tout d’abord, enregistrez et quittez nano en appuyant sur CTRL+X.

      De retour dans votre terminal, lancez votre application avec la commande node :

      Votre terminal affichera la sortie suivante :

      Output

      stdout: total 4.0K -rw-rw-r-- 1 sammy sammy 280 Jul 27 16:35 listFiles.js

      Ceci liste le contenu du répertoire child-processes en format long, ainsi que la taille du contenu en haut. Vos résultats auront votre propre utilisateur et groupe à la place de sammy. Cela montre que le programme listFiles.js a exécuté avec succès la commande shell ls -lh.

      Voyons maintenant un autre moyen d’exécuter des processus concurrents. Le module child_process de Node.js peut également exécuter des fichiers exécutables avec la fonction execFile(). La différence essentielle entre les fonctions execFile() et exec() est que le premier argument execFile() est maintenant un chemin vers un fichier exécutable au lieu d’une commande. La sortie du fichier exécutable est stockée dans un buffer comme exec(), que nous accédons via une fonction de rappel avec les paramètres error, stdout et stderr.

      Remarque : les scripts dans Windows comme .bat et .cmd ne peuvent pas être exécutés avec execFile() car la fonction ne crée pas de shell lors de l’exécution du fichier. Sous Unix, Linux et macOS, les scripts exécutables n’ont pas toujours besoin d’un shell s’exécuter. Cependant, une machine Windows a besoin d’un shell pour exécuter des scripts. Pour exécuter des fichiers script sous Windows, utilisez exec(), puisqu’il crée un nouveau shell. Vous pouvez également utiliser spawn(), que vous utiliserez plus loin dans cette étape.

      Cependant, notez que vous pouvez exécuter avec succès des fichiers .exe dans Windows en utilisant execFile(). Cette limitation ne s’applique qu’aux fichiers script qui nécessitent un shell pour s’exécuter.

      Commençons par ajouter un script exécutable pour exécuter execFile(). Nous allons écrire un script bash qui téléchargera le logo Node.js du site Node.js et Base64 l’encode pour convertir ses données en une chaîne de caractères ASCII.

      Créez un nouveau fichier script shell appelé processNodejsImage.sh :

      • nano processNodejsImage.sh

      Écrivez maintenant un script pour télécharger l’image et base64 la convertit :

      ~/child-processes/processNodejsImage.sh

      #!/bin/bash
      curl -s https://nodejs.org/static/images/logos/nodejs-new-pantone-black.svg > nodejs-logo.svg
      base64 nodejs-logo.svg
      

      La première déclaration est une déclaration shebang. Elle est utilisée dans Unix, Linux et macOS lorsque nous voulons spécifier un shell pour exécuter notre script. La deuxième déclaration est une commande curl. L’utilitaire cURL, dont la commande est curl, est un outil en ligne de commande qui peut transférer des données vers et en provenance d’un serveur. Nous utilisons cURL pour télécharger le logo Node.js du site web, et nous utilisons redirection pour enregistrer les données téléchargées dans un nouveau fichier nodejs-logo.svg. La dernière déclaration utilise l’utilitaire base64 pour encoder le fichier nodejs-logo.svg que nous avons téléchargé avec cURL. Le script envoie alors la chaîne encodée à la console.

      Enregistrez et quittez avant de continuer.

      Pour que notre programme Node exécute le script bash, nous devons le rendre exécutable. Pour ce faire, lancez ce qui suit :

      • chmod u+x processNodejsImage.sh

      Cela donnera à votre utilisateur actuel la permission d’exécuter le fichier.

      Une fois notre script en place, nous pouvons écrire un nouveau module Node.js pour l’exécuter. Ce script utilisera execFile() pour exécuter le script dans un processus enfant, en détectant toute erreur et en affichant toute sortie sur la console.

      Sur votre terminal, créez un nouveau fichier JavaScript appelé getNodejsImage.js :

      Tapez le code suivant dans l’éditeur de texte :

      ~/child-processes/getNodejsImage.js

      const { execFile } = require('child_process');
      
      execFile(__dirname + '/processNodejsImage.sh', (error, stdout, stderr) => {
        if (error) {
          console.error(`error: ${error.message}`);
          return;
        }
      
        if (stderr) {
          console.error(`stderr: ${stderr}`);
          return;
        }
      
        console.log(`stdout:n${stdout}`);
      });
      

      Nous utilisons la déstructuration JavaScript pour importer la fonction execFile() du module child_process. Nous utilisons alors cette fonction, en passant le chemin du fichier comme prénom. __dirname contient le chemin du répertoire du module dans lequel il est écrit. Node.js fournit la variable __dirname à un module lorsque le module s’exécute. En utilisant __dirname, notre script trouvera toujours le fichier processNodejsImage.sh sur différents systèmes d’exploitation, quel que soit l’endroit où nous exécutons getNodejsImage.js. Notez que pour la configuration actuelle de notre projet, getNodejsImage.js et processNodejsImage.sh doivent se trouver dans le même dossier.

      Le deuxième argument est un rappel avec les paramètres error, stdout, et stderr. Comme avec notre exemple précédent qui a utilisé exec(), nous vérifions chaque sortie possible du fichier script et les enregistrons dans la console.

      Dans votre éditeur de texte, enregistrez ce fichier et quittez l’éditeur.

      Dans votre terminal, utilisez node pour exécuter le module:

      L’exécution de ce script produira une sortie comme celle-ci :

      Output

      stdout: PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNDQyLjQgMjcwLjkiPjxkZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0iYiIgeDE9IjE4MC43IiB5MT0iODAuNyIge ...

      Notez que nous avons tronqué la sortie dans cet article en raison de sa grande taille.

      Avant d’encoder l’image en base64, processNodejsImage.sh la télécharge. Vous pouvez également vérifier que vous avez téléchargé l’image en inspectant le répertoire actuel.

      Exécutez listFiles.js pour trouver la liste mise à jour des fichiers dans notre répertoire :

      Le script affichera un contenu similaire à celui qui suit sur le terminal :

      Output

      stdout: total 20K -rw-rw-r-- 1 sammy sammy 316 Jul 27 17:56 getNodejsImage.js -rw-rw-r-- 1 sammy sammy 280 Jul 27 16:35 listFiles.js -rw-rw-r-- 1 sammy sammy 5.4K Jul 27 18:01 nodejs-logo.svg -rwxrw-r-- 1 sammy sammy 129 Jul 27 17:56 processNodejsImage.sh

      Nous avons maintenant exécuté avec succès processNodejsImage.sh en tant que processus enfant dans Node.js en utilisant la fonction execFile().

      Les fonctions exec() et execFile() peuvent exécuter des commandes sur le shell du système d’exploitation dans un processus enfant Node.js. Node.js fournit également une autre méthode avec une fonctionnalité similaire, spawn(). La différence est qu’au lieu d’obtenir la sortie des commandes shell en même temps, nous les recevons en morceaux via un flux. Dans la section suivante, nous utiliserons la commande spawn() pour créer un processus enfant.

      Étape 2 — Création d’un processus enfant avec spawn()

      La fonction spawn() exécute une commande dans un processus. Cette fonction renvoie des données via l’API stream. Par conséquent, pour obtenir la sortie du processus enfant, nous devons écouter les événements du flux.

      Les flux dans Node.js sont des instances d’émetteurs d’événements. Si vous souhaitez en savoir plus sur l’écoute des événements et les fondements de l’interaction avec les flux, vous pouvez lire notre guide sur Utiliser des émetteurs d’événements dans Node.js.

      Il est souvent judicieux de choisir spawn() plutôt exec() ou execFile() lorsque la commande que vous voulez exécuter peut produire une grande quantité de données. Avec un buffer, tel qu’utilisé par exec() et execFile(), toutes les données traitées sont stockées dans la mémoire de l’ordinateur. Pour de grandes quantités de données, cela peut dégrader la performance du système. Avec un flux, les données sont traitées et transférées en petits groupes. Par conséquent, vous pouvez traiter une grande quantité de données sans utiliser trop de mémoire à la fois.

      Voyons comment nous pouvons utiliser spawn() pour créer un processus enfant. Nous allons écrire un nouveau module Node.js qui crée un processus enfant pour exécuter la commande find. Nous utiliserons la commande find pour lister tous les fichiers du répertoire actuel.

      Créez un nouveau fichier appelé findFiles.js :

      Dans votre éditeur de texte, commencez par appeler la commande spawn() :

      ~/child-processes/findFiles.js

      const { spawn } = require('child_process');
      
      const child = spawn('find', ['.']);
      

      Nous avons d’abord importé la fonction spawn() du module child_process. Nous avons ensuite appelé la fonction spawn() pour créer un processus enfant qui exécute la commande find. Nous tenons la référence au processus dans la variable child, que nous utiliserons pour écouter ses événements en flux.

      Le premier argument en spawn() est la commande à exécuter, dans ce cas find. Le deuxième argument est un tableau qui contient les arguments pour la commande exécutée. Dans ce cas, nous disons à Node.js d’exécuter la commande find avec l’argument ., ce qui fait que la commande trouve tous les fichiers du répertoire courant. La commande équivalente dans le terminal est find ..

      Avec les fonctions exec() et execFile(), nous avons écrit les arguments en même temps que la commande dans une seule chaîne. Cependant, avec spawn(), tous les arguments des commandes doivent être entrés dans le tableau. Ceci parce que spawn(), contrairement à exec() et execFile(), ne crée pas de nouveau shell avant d’exécuter un processus. Pour avoir des commandes avec leurs arguments dans une seule chaîne, vous devez également créer un nouveau shell.

      Continuons notre module en ajoutant des auditeurs pour la sortie de la commande. Ajoutez les lignes surlignées suivantes :

      ~/child-processes/findFiles.js

      const { spawn } = require('child_process');
      
      const child = spawn('find', ['.']);
      
      child.stdout.on('data', data => {
        console.log(`stdout:n${data}`);
      });
      
      child.stderr.on('data', data => {
        console.error(`stderr: ${data}`);
      });
      

      Les commandes peuvent renvoyer des données dans le flux stdout ou dans le flux stderr, vous avez donc ajouté des auditeurs pour les deux. Vous pouvez ajouter des écouteurs en appelant la méthode on() des objets de chaque flux. L’événement data des flux nous donne la sortie de la commande vers ce flux. Chaque fois que nous obtenons des données sur l’un ou l’autre des flux, nous les enregistrons dans la console.

      Nous écoutons ensuite deux autres événements : l’événement error si la commande ne s’exécute pas ou est interrompue, et l’événement close lorsque la commande a fini d’exécuter, fermant ainsi le flux.

      Dans l’éditeur de texte, complétez le module Node.js en écrivant les lignes en surbrillance suivantes :

      ~/child-processes/findFiles.js

      const { spawn } = require('child_process');
      
      const child = spawn('find', ['.']);
      
      child.stdout.on('data', (data) => {
        console.log(`stdout:n${data}`);
      });
      
      child.stderr.on('data', (data) => {
        console.error(`stderr: ${data}`);
      });
      
      child.on('error', (error) => {
        console.error(`error: ${error.message}`);
      });
      
      child.on('close', (code) => {
        console.log(`child process exited with code ${code}`);
      });
      

      Pour les événements error et close, vous configurez un auditeur directement sur la variable child. Lors de l’écoute des événements error, si une erreur survient, Node.js fournit un objet Error. Dans ce cas, vous enregistrez la propriété message de l’erreur.

      Lorsqu’il écoute l’événement close, Node.js fournit le code exit de la commande. Un code exit indique si la commande s’est exécuté avec succès ou non. Lorsqu’une commande s’exécute sans erreurs, elle renvoie la valeur la plus basse possible pour un code exit : 0. Lorsqu’elle s’exécute avec une erreur, elle renvoie un code différent de zéro.

      Le module est terminé. Enregistrez et quittez nano avec CTRL+X.

      Maintenant, lancez le code avec la commande node :

      Une fois terminé, vous verrez la sortie suivante :

      Output

      stdout: . ./findFiles.js ./listFiles.js ./nodejs-logo.svg ./processNodejsImage.sh ./getNodejsImage.js child process exited with code 0

      Nous trouvons une liste de tous les fichiers dans notre répertoire actuel et le code exit de la commande, qui est 0 car il s’est exécuté avec succès. Bien que notre répertoire actuel contienne un petit nombre de fichiers, si nous avions exécuté ce code dans notre répertoire d’origine, notre programme aurait énuméré chaque fichier dans chaque dossier accessible à notre utilisateur. Étant donné que la sortie peut être potentiellement très importante, l’utilisation de la fonction spawn() est la plus idéale car ses flux ne nécessitent pas autant de mémoire qu’un grand buffer.

      Jusqu’à présent, nous avons utilisé des fonctions pour créer des processus enfants afin d’exécuter des commandes externes dans notre système d’exploitation. Node.js fournit également un moyen de créer un processus enfant qui exécute d’autres programmes Node.js. Utilisons la fonction fork() pour créer un processus enfant pour un module Node.js dans la section suivante.

      Étape 3 — Création d’un processus enfant avec fork()

      Node.js fournit la fonction fork(), une variation du spawn(), pour créer un processus enfant qui est également un processus Node.js. Le principal avantage d’utiliser fork() pour créer un processus Node.js par rapport à spawn() ou exec() est que fork() permet la communication entre le processus parent et le processus enfant.

      Avec fork(), en plus de récupérer des données du processus enfant, un processus parent peut envoyer des messages au processus enfant en cours d’exécution. De la même façon, le processus enfant peut envoyer des messages au processus parent.

      Voyons un exemple où l’utilisation de fork() pour créer un nouveau processus enfant Node.js peut améliorer les performances de notre application. Les programmes Node.js s’exécutent sur un seul processus. Par conséquent, les tâches gourmandes en CPU comme l’itération sur des grandes boucles ou l’analyse de gros fichiers JSON empêchent l’exécution d’autres codes JavaScript. Pour certaines applications, ce n’est pas une option viable. Si un serveur web est bloqué, il ne peut pas traiter de nouvelles demandes entrantes tant que le code qui le bloque n’a pas fini son exécution.

      Voyons cela en pratique en créant un serveur web avec deux points terminaux. L’un d’eux effectuera un calcul lent qui bloquera le processus Node.js. L’autre point terminal renverra un objet JSON disant hello.

      Tout d’abord, créez un nouveau fichier appelé httpServer.js, qui contiendra le code de notre serveur HTTP :

      Nous commencerons par configurer le serveur HTTP. Cela implique l’importation du module http, la création d’une fonction d’écoute des requêtes, la création d’un objet serveur et l’écoute des requêtes sur l’objet serveur. Si vous souhaitez vous plonger plus profondément dans la création de serveurs HTTP dans Node.js ou si vous souhaitez vous rafraîchir la mémoire, vous pouvez lire notre guide Comment créer un serveur web en Node.js avec le module HTTP.

      Entrez le code suivant dans votre éditeur de texte pour configurer un serveur HTTP :

      ~/child-processes/httpServer.js

      const http = require('http');
      
      const host="localhost";
      const port = 8000;
      
      const requestListener = function (req, res) {};
      
      const server = http.createServer(requestListener);
      server.listen(port, host, () => {
        console.log(`Server is running on http://${host}:${port}`);
      });
      

      Ce code met en place un serveur HTTP qui s’exécutera sur http://localhost:8000. Elle utilise des littéraux de gabarits pour générer dynamiquement cette URL.

      Ensuite, nous allons écrire une fonction intentionnellement lente qui compte 5 milliards de fois dans une boucle. Avant la fonction requestListener(), ajoutez le code suivant :

      ~/child-processes/httpServer.js

      ...
      const port = 8000;
      
      const slowFunction = () => {
        let counter = 0;
        while (counter < 5000000000) {
          counter++;
        }
      
        return counter;
      }
      
      const requestListener = function (req, res) {};
      ...
      

      Cela utilise la syntaxe de fonction fléchée pour créer une boucle while qui compte jusqu’à 5000000000.

      Pour terminer ce module, nous devons ajouter du code à la fonction requestListener(). Notre fonction appellera la fonction slowFunction() sur le sous-chemin et renverra un petit message JSON pour l’autre. Ajoutez le code suivant au module :

      ~/child-processes/httpServer.js

      ...
      const requestListener = function (req, res) {
        if (req.url === '/total') {
          let slowResult = slowFunction();
          let message = `{"totalCount":${slowResult}}`;
      
          console.log('Returning /total results');
          res.setHeader('Content-Type', 'application/json');
          res.writeHead(200);
          res.end(message);
        } else if (req.url === '/hello') {
          console.log('Returning /hello results');
          res.setHeader('Content-Type', 'application/json');
          res.writeHead(200);
          res.end(`{"message":"hello"}`);
        }
      };
      ...
      

      Si l’utilisateur atteint le serveur dans le sous-chemin /total, nous exécutons alors slowFunction(). Si nous atteignons le sous-chemin /hello, nous renvoyons ce message JSON : {"message":"hello"}.

      Enregistrez et quittez le fichier en appuyant sur CTRL+X.

      Pour tester, lancez ce module serveur avec node :

      Lorsque notre serveur démarre, la console affiche ce qui suit :

      Output

      Server is running on http://localhost:8000

      Maintenant, pour tester les performances de notre module, ouvrez deux terminaux supplémentaires. Sur le premier terminal, utilisez la commande curl pour faire une requête vers le point terminal /total, qui devrait être lent:

      • curl http://localhost:8000/total

      Dans l’autre terminal, utilisez curl pour faire une requête au point terminal /hello :

      • curl http://localhost:8000/hello

      La première requête renverra le JSON suivant :

      Output

      {"totalCount":5000000000}

      Alors que la deuxième requête renverra ce JSON :

      Output

      {"message":"hello"}

      La requête à /hello est complétée qu’après la requête à /total. Le slowFunction() a bloqué l’exécution de tout autre code alors qu’il se trouvait toujours dans sa boucle. Vous pouvez vérifier cela en regardant la sortie du serveur Node.js qui a été enregistrée dans votre terminal original :

      Output

      Returning /total results Returning /hello results

      Pour traiter le code de blocage tout en acceptant les requêtes entrantes, nous pouvons déplacer le code de blocage vers un processus enfant avec fork(). Nous allons déplacer le code de blocage dans son propre module. Le serveur Node.js créera alors un processus enfant lorsque quelqu’un accède au point terminal /total et écoutera les résultats de ce processus enfant.

      Remaniez le serveur en créant d’abord un nouveau module appelé getCount.js qui contiendra slowFunction() :

      Entrez encore une fois le code pour slowFunction() :

      ~/child-processes/getCount.js

      const slowFunction = () => {
        let counter = 0;
        while (counter < 5000000000) {
          counter++;
        }
      
        return counter;
      }
      

      Comme ce module sera un processus enfant créé avec fork(), nous pouvons également ajouter du code pour communiquer avec le processus parent lorsque slowFunction() a terminé le traitement. Ajoutez le bloc de code suivant qui envoie un message au processus parent avec le JSON à retourner à l’utilisateur :

      ~/child-processes/getCount.js

      const slowFunction = () => {
        let counter = 0;
        while (counter < 5000000000) {
          counter++;
        }
      
        return counter;
      }
      
      process.on('message', (message) => {
        if (message == 'START') {
          console.log('Child process received START message');
          let slowResult = slowFunction();
          let message = `{"totalCount":${slowResult}}`;
          process.send(message);
        }
      });
      

      Décomposons ce bloc de code. Les messages entre un processus parent et un processus enfant créés par fork() sont accessibles via l’objet process global Node.js. Nous ajoutons un auditeur à la variable process pour rechercher des événements message. Une fois que nous recevons un événement message, nous vérifions si c’est l’événement START. Notre code serveur enverra l’événement START lorsque quelqu’un accède au point terminal /total. À la réception de cet événement, nous exécutons slowFunction() et créons une chaîne JSON avec le résultat de la fonction. Nous utilisons process.send() pour envoyer un message au processus parent.

      Enregistrez et quittez getCount.js en entrant CTRL+X dans nano.

      Maintenant, modifions le fichier httpServer.js afin qu’au lieu d’appeler slowFunction(), il crée un processus enfant qui exécute getCount.js.

      Rouvrez httpServer.js avec nano :

      Tout d’abord, importez la fonction fork() du module child_process :

      ~/child-processes/httpServer.js

      const http = require('http');
      const { fork } = require('child_process');
      ...
      

      Ensuite, nous allons supprimer la fonction slowFunction() de ce module et modifier la fonction requestListener() pour créer un processus enfant. Modifiez le code dans votre fichier afin qu’il ressemble à ceci :

      ~/child-processes/httpServer.js

      ...
      const port = 8000;
      
      const requestListener = function (req, res) {
        if (req.url === '/total') {
          const child = fork(__dirname + '/getCount');
      
          child.on('message', (message) => {
            console.log('Returning /total results');
            res.setHeader('Content-Type', 'application/json');
            res.writeHead(200);
            res.end(message);
          });
      
          child.send('START');
        } else if (req.url === '/hello') {
          console.log('Returning /hello results');
          res.setHeader('Content-Type', 'application/json');
          res.writeHead(200);
          res.end(`{"message":"hello"}`);
        }
      };
      ...
      

      Lorsque quelqu’un passe au point terminal /total, nous créons maintenant un nouveau processus enfant avec fork(). L’argument de fork() est le chemin vers le module Node.js. Dans ce cas, il s’agit du fichier getCount.js dans notre répertoire actuel, que nous recevons de __dirname. La référence à ce processus enfant est stockée dans une variable child.

      Nous ajoutons alors un auditeur à l’objet child. Cet auditeur capture tous les messages que le processus enfant nous donne. Dans ce cas, getCount.js renverra une chaîne JSON avec le nombre total compté par la boucle while. Lorsque nous recevons ce message, nous envoyons le JSON à l’utilisateur.

      Nous utilisons la fonction send() de la variable child pour lui donner un message. Ce programme envoie le message START, qui commence l’exécution de slowFunction() dans le processus enfant.

      Enregistrez et quittez nano en entrant CTRL+X.

      Pour tester l’amélioration en utilisant fork() fait sur le serveur HTTP, commencez par exécuter le fichier httpServer.js avec node :

      Comme auparavant, il sortira le message suivant lorsqu’il se lancera :

      Output

      Server is running on http://localhost:8000

      Pour tester le serveur, nous aurons besoin de deux terminaux supplémentaires, comme nous l’avons fait la première fois. Vous pouvez les réutiliser s’ils sont toujours ouverts.

      Dans le premier terminal, utilisez la commande curl pour faire une requête vers le point terminal /total, qui prend un certain temps à calculer :

      • curl http://localhost:8000/total

      Dans l’autre terminal, utilisez curl pour faire une requête au point terminal /hello, qui répond en peu de temps :

      • curl http://localhost:8000/hello

      La première requête renverra le JSON suivant :

      Output

      {"totalCount":5000000000}

      Alors que la deuxième requête renverra ce JSON :

      Output

      {"message":"hello"}

      Contrairement à la première fois que nous avons essayé ceci, la deuxième requête vers /hello s’exécute immédiatement. Vous pouvez confirmer en examinant les journaux, qui ressembleront à ceci :

      Output

      Child process received START message Returning /hello results Returning /total results

      Ces journaux montrent que la requête concernant le point terminal /hello s’est exécutée après la création du processus enfant, mais avant que celui-ci n’ait terminé sa tâche.

      Comme nous avons déplacé le code de blocage dans un processus enfant en utilisant fork(), le serveur a toujours pu répondre à d’autres requêtes et exécuter d’autres codes JavaScript. Grâce à la capacité de la fonction fork() à transmettre des messages, nous pouvons contrôler le moment où un processus enfant commence une activité et nous pouvons renvoyer des données d’un processus enfant à un processus parent.

      Conclusion

      Dans cet article, vous avez utilisé diverses fonctions pour créer un processus enfant dans Node.js. Vous avez d’abord créé des processus enfants avec exec() pour exécuter des commandes shell à partir du code Node.js. Vous avez ensuite exécuté un fichier exécutable avec la fonction execFile(). Vous avez examiné la fonction spawn(), qui peut également exécuter des commandes mais renvoie des données via un flux et ne démarre pas un shell comme exec() et execFile(). Enfin, vous avez utilisé la fonction fork() pour permettre une communication bidirectionnelle entre les processus parent et enfant.

      Pour en savoir plus sur le module child_process, vous pouvez lire la documentation Node.js. Si vous souhaitez continuer à apprendre Node.js, vous pouvez revenir à la série Comment coder dans Node.js, ou parcourir les projets de programmation et les configurations sur notre page thématique Node.



      Source link