One place for hosting & domains

      processos

      Como usar o ps, kill e nice para gerenciar processos no Linux


      Introdução


      Um servidor Linux, assim como qualquer outro computador que você conheça, executa aplicativos. Para o computador, eles são considerados “processos”.

      Embora o Linux lide nos bastidores com o gerenciamento de baixo nível no ciclo de vida de um processo, você precisará de uma maneira de interagir com o sistema operacional para gerenciá-lo de um nível superior.

      Neste guia, vamos discutir alguns aspectos simples do gerenciamento de processos. O Linux oferece uma coleção abundante de ferramentas para esse propósito.

      Vamos explorar essas ideias em um VPS Ubuntu 12.04, mas qualquer distribuição moderna do Linux funcionará de maneira similar.

      Como visualizar processos em execução no Linux


      top


      A maneira mais fácil de descobrir quais processos estão sendo executados no seu servidor é executando o comando 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 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.07 ksoftirqd/0            6 root      RT   0     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          
      

      A porção superior de informações mostra estatísticas do sistema, como a carga do sistema e o número total de tarefas.

      É possível ver facilmente que há 1 processo em execução e 55 processos estão suspensos (ou seja, ociosos/sem utilizar recursos da CPU).

      A parte inferior mostra os processos em execução e suas estatísticas de uso.

      htop


      Uma versão melhorada de top, chamada htop, está disponível nos repositórios. Instale-o com este comando:

      sudo apt-get install htop
      

      Se executarmos o comando htop, veremos que as informação são exibidas de uma maneira mais inteligível:

      htop***
      
        Mem[|||||||||||           49/995MB]     Load average: 0.00 0.03 0.05   CPU[                          0.0%]     Tasks: 21, 3 thr; 1 running   Swp[                         0/0MB]     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
      

      Aprenda mais sobre como usar o top e htop aqui.

      Como usar o ps para listar processos


      Tanto o top quanto o htop fornecem uma interface agradável para visualizar processos em execução, de maneira semelhante a um gerenciador de tarefas gráfico.

      No entanto, essas ferramentas nem sempre são flexíveis o suficiente para abranger adequadamente todos os cenários. Um comando poderoso chamado ps é geralmente a resposta para esses problemas.

      Quando chamado sem argumentos, o resultado pode ser um pouco confuso:

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

      Esse resultado mostra todos os processos associados ao usuário e sessão de terminal atuais. Isso faz sentido porque estamos executando apenas o bash e ps com esse terminal atualmente.

      Para conseguirmos uma visão mais completa dos processos neste sistema, podemos executar o seguinte:

      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] . . .
      

      Essas opções dizem ao ps para mostrar processos de propriedade de todos os usuários (independentemente da sua associação de terminais) em um formato facilmente inteligível.

      Para ver um modo de exibição em árvore, onde relações hierárquicas são mostradas, executamos o comando com essas opções:

      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] . . .
      

      Como se vê, o processo kthreadd é mostrado como um pai do processo ksoftirqd/0 e de outros.

      Uma nota sobre IDs de processo


      No Linux e em sistemas do tipo Unix, cada processo é atribuído a um ID de processo, ou PID. É assim que o sistema operacional identifica e mantém o controle dos processos.

      Uma maneira rápida de obter o PID de um processo é com o comando pgrep:

      pgrep bash***
      
      1017
      

      Isso irá simplesmente consultar o ID do processo e retorná-lo.

      Ao primeiro processo gerado na inicialização do sistema, chamado init, é atribuído o PID de “1″.

      pgrep init***
      
      1
      

      Esse processo é então responsável por gerar todos os processos no sistema. Os processos posteriores recebem números PID maiores.

      O pai de um processo é o processo que foi responsável por gerá-lo. Os processos pais possuem um PPID, que pode ser visto no cabeçalho da coluna correspondente em muitos aplicativos de gerenciamento de processos, incluindo o top, htop e ps.

      Qualquer comunicação entre o usuário e o sistema operacional em relação aos processos envolve a tradução entre os nomes de processos e PIDs em algum ponto durante a operação. É por isso que os utilitários informam o PID.

      Relacionamentos pai/filho


      A criação de um processo filho acontece em dois passos: fork(), que cria espaços de endereço e copia os recursos de propriedade do pai via copia em gravação para que fique disponível ao processo filho; e exec(), que carrega um executável no espaço de endereço e o executa.

      Caso um processo filho morra antes do seu pai, o filho torna-se um zumbi até que o pai tenha coletado informações sobre ele ou indicado ao kernel que ele não precisa dessas informações. Os recursos do processo filho serão então libertados. No entanto, se o processo pai morrer antes do filho, o filho será adotado pelo init, embora também possa ser reatribuído a outro processo.

      Como enviar sinais de processos no Linux


      Todos os processos no Linux respondem a sinais. Os sinais são uma maneira ao nível de SO de dizer aos programas para terminarem ou modificarem seu comportamento.

      Como enviar sinais de processos por PID


      A maneira mais comum de passar sinais para um programa é com o comando kill.

      Como se espera, a funcionalidade padrão desse utilitário é tentar encerrar um processo:

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

      Isso envia o sinal TERM ao processo. O sinal TERM pede ao processo para encerrar. Isso permite que o programa execute operações de limpeza e seja finalizado sem problemas.

      Se o programa estiver se comportando incorretamente e não se encerrar quando um sinal TERM for passado, podemos escalar o sinal passando o sinal KILL:

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

      Esse é um sinal especial que não é enviado ao programa.

      Ao invés disso, ele é dado ao kernel do sistema operacional, que encerra o processo. Isso é usado para passar por cima de programas que ignoram os sinais que lhe são enviados.

      Cada sinal possui um número associado que pode ser passado ao invés de seu nome. Por exemplo, é possível passar “-15″ ao invés de ”-TERM” e ”-9″ ao invés de ”-KILL”.

      Como usar sinais para outros fins


      Os sinais não são usados apenas para encerrar programas. Eles também podem ser usados para realizar outras ações.

      Por exemplo, muitos daemons são reiniciados quando recebem o HUP, ou sinal de desligamento. O Apache é um programa que funciona dessa forma.

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

      O comando acima fará com que o Apache recarregue seu arquivo de configuração e retome o serviço de conteúdo.

      Liste todos os sinais que podem ser enviados com o kill digitando:

      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 . . .
      

      Como enviar sinais de processos pelo nome


      Embora a maneira convencional de enviar sinais seja através do uso de PIDs, também existem métodos para fazer isso com os nomes regulares dos processos.

      O comando pkill funciona de maneira bastante similar ao kill, com a diferença de operar com um nome de processo:

      pkill -9 ping
      

      O comando acima é equivalente a:

      kill -9 `pgrep ping`
      

      Se quiser enviar um sinal para todas as instâncias de um determinado processo, utilize o comando killall:

      killall firefox
      

      O comando acima enviará o sinal TERM para todas as instâncias do firefox em execução no computador.

      Como ajustar prioridades de processos


      Geralmente, é desejável ajustar quais processos recebem prioridade em um ambiente de servidor.

      Alguns processos podem ser considerados críticos para sua situação, enquanto outros podem ser executados sempre que houver recursos sobrando no sistema.

      O Linux controla a prioridade através de um valor chamado niceness (gentileza).

      As tarefas de alta prioridade são consideradas menos nice (gentis), porque não compartilham recursos tão bem. Por outro lado, os processos de baixa prioridade são nice, porque insistem em utilizar apenas a menor quantidade de recursos.

      Quando executamos o top no início do artigo, havia uma coluna marcada “NI”. Esse é o valor de gentileza do processo:

      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
      

      Os valores de gentileza podem variar entre “-19/-20” (prioridade mais alta) e ”19/20” (prioridade mais baixa), dependendo do sistema.

      Para executar um programa com um certo valor de gentileza, podemos usar o comando nice:

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

      Isso funciona apenas ao iniciar um novo programa.

      Para alterar o valor de gentileza de um programa que já está sendo executado, usamos uma ferramenta chamada renice:

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

      Nota: embora o comando nice opere necessariamente com um nome de comando, o renice opera chamando o PID do processo

      Conclusão


      O gerenciamento de processos é um tópico que pode ser de difícil compreensão para novos usuários, pois as ferramentas usadas são diferentes de seus equivalentes gráficos.

      No entanto, as ideias são habituais e intuitivas e, com um pouco de prática, se tornarão naturais. Como os processos estão envolvidos em tudo o que é feito em um sistema de computador, aprender como controlá-los de maneira eficaz é uma habilidade essencial.

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



      Source link

      Como lançar processos filhos no Node.js


      O autor selecionou a COVID-19 Relief Fund para receber uma doação como parte do programa Write for DOnations.

      Introdução

      Quando um usuário executa um único programa no Node.js, ele é executado como um único processo de sistema operacional (OS) que representa a instância do programa em execução. Dentro desse processo, o Node.js executa programas em uma única thread. Como mencionado anteriormente nesta série com o tutorial How To Write Asynchronous Code in Node.js, como apenas uma thread pode ser executada em um processo, operações que levam muito tempo para ser executadas em JavaScript podem bloquear a thread do Node.js e atrasar a execução de outro código. Uma estratégia importante para contornar este problema é lançar um processo filho, ou um processo criado por outro processo, quando confrontado com tarefas de longa execução. Quando um novo processo é lançado, o sistema operacional pode empregar técnicas de multiprocessamento para garantir que o processo principal do Node.js e o processo filho adicional executem concorrentemente ou ao mesmo tempo.

      O Node.js inclui o módulo child_process, que possui funções para criar novos processos. Além de lidar com tarefas de longa execução, este módulo também pode interagir com o SO e executar comandos do shell. Administradores de sistema podem usar o Node.js para executar comandos do shell para estruturar e manter suas operações como um módulo Node.js em vez de scripts de shell.

      Neste tutorial, você criará processos filhos ao executar uma série de aplicações de exemplo do Node.js. Você criará processos com o módulo child_process recuperando os resultados de um processo filho através de um buffer ou uma string com a função exec(), e depois a partir de uma stream de dados com a função spawn(). Você terminará usando o fork() para criar um processo filho de outro programa Node.js com o qual você pode se comunicar durante a execução. Para ilustrar esses conceitos, você escreverá um programa para listar o conteúdo de um diretório, um programa para encontrar arquivos, e um servidor Web com vários endpoints.

      Pré-requisitos

      Os desenvolvedores geralmente criam processos filhos para executar comandos no sistema operacional quando precisam manipular a saída de dos programas Node.js com o shell, como ao usar pipes ou redirecionamento. A função exec() no Node.js cria um novo processo de shell e executa um comando nele. A saída do comando é mantida em um buffer na memória, que você pode aceitar através de uma função de callback passada dentro de exec().

      Vamos começar criando os primeiros processos filhos no Node.js. Primeiro, precisamos configurar o ambiente de codificação para armazenar os scripts que vamos criar ao longo deste tutorial. No terminal, crie uma pasta chamada child-processes:

      Entre nessa pasta no terminal com o comando cd:

      Crie um novo arquivo chamado listFiles.js e abra o arquivo em um editor de texto. Neste tutorial, usaremos o nano, um editor de texto para terminal:

      Vamos escrever um módulo Node.js que usa a função exec() para executar o comando ls. O comando ls lista os arquivos e pastas em um diretório. Este programa pega a saída do comando ls e a exibe ao usuário.

      No editor de texto, adicione o seguinte código:

      ~/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}`);
      });
      

      Primeiro importamos o comando exec() do módulo child_process usando JavaScript destructuring. Depois de importado, usamos a função exec(). O primeiro argumento é o comando que gostaríamos de executar. Neste caso, ele é o ls -lh, que lista todos os arquivos e pastas no diretório atual em formato longo, com um tamanho total de arquivo em unidades legíveis por humanos no topo da saída.

      O segundo argumento é uma função de callback com três parâmetros: error, stdout e stderr. Se o comando falhou ao executar, error irá capturar a razão pela qual ele falhou. Isso pode acontecer se o shell não puder encontrar o comando que você está tentando executar. Se o comando for executado com sucesso, todos os dados que ele escreve na stream de saída padrão são capturados no stdout, e todos os dados que ele escreve na stream de erro padrão são capturados no stderr.

      Nota: é importante manter a diferença entre error e stderr em mente. Se o comando em si falhar ao executar, error irá capturar o erro. Se o comando executar, mas retornar a saída para a stream de erro, o stderr irá capturá-lo. Os programas Node.js mais resilientes irão lidar com todas as saídas possíveis para um processo filho.

      Na função de callback, primeiro verificamos se recebemos um erro. Se sim, exibimos a message do erro (uma propriedade do objeto Error) com console.error() e terminamos a função com return. Em seguida, verificamos se o comando imprimiu uma mensagem de erro e usamos return se positivo. Se o comando for executado com sucesso, registramos a saída no console com console.log().

      Vamos executar este arquivo para vê-lo em ação. Primeiro, salve e saia do nano pressionando CTRL+X.

      Volte ao seu terminal, execute sua aplicação com o comando node:

      Seu terminal irá exibir a seguinte saída:

      Output

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

      Isso lista o conteúdo do diretório child-processes em formato longo, juntamente com o tamanho do conteúdo no topo. Seus resultados terão seu próprio usuário e grupo no lugar de sammy. Isso mostra que o programa listFiles.js executou com sucesso o comando de shell ls -lh.

      Agora, vamos olhar para uma outra maneira de executar processos simultâneos. O módulo child_process do Node.js também pode rodar arquivos executáveis com a função execFile(). A diferença principal entre as funções execFile() e exec() é que o primeiro argumento de execFile() é agora um caminho para um arquivo executável em vez de um comando. A saída do arquivo executável é armazenada em um buffer como exec(), que acessamos através de uma função de callback com parâmetros error, stdout e stderr.

      Nota: os scripts no Windows, como os arquivos .bat e .cmd, não podem ser executados com execFile(), porque a função não cria um shell ao executar o arquivo. No Unix, Linux e macOS, scripts executáveis nem sempre precisam de um shell para serem executados. No entanto, uma máquina Windows precisa de um shell para executar scripts. Para executar arquivos de script no Windows use exec(), uma vez que ele cria um novo shell. Alternativamente, você pode usar spawn(), que você usará mais tarde neste Passo.

      No entanto, observe que você pode executar arquivos .exe no Windows usando execFile(). Essa limitação se aplica apenas a arquivos de script que exigem um shell para serem executados.

      Vamos começar adicionando um script executável para o execFile() executar. Escreveremos um script bash que baixa o logotipo do Node.js a partir do site do Node.js e o Base64 o codifica para converter seus dados para uma string de caracteres ASCII.

      Crie um novo script de shell chamado processNodejsImage.sh:

      • nano processNodejsImage.sh

      Agora, escreva um script para baixar a imagem e o base64 convertê-la:

      ~/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
      

      A primeira declaração é uma declaração de shebang. Ela é usada no Unix, Linux e macOS quando queremos especificar um shell para executar nosso script. A segunda declaração é um comando curl. O utilitário cURL, cujo comando é curl, é uma ferramenta de linha de comando que pode transferir dados de e para um servidor. Usamos o cURL para baixar o logotipo do Node.js a partir do site e, em seguida, usamos o redirecionamento para salvar os dados baixados para um novo arquivo nodejs-logo.svg. A última declaração usa o utilitário base64 para codificar o arquivo nodejs-logo.svg que baixamos com o cURL. O script então entrega a string codificada para o console.

      Salve e saia antes de continuar.

      Para que nosso programa Node execute o script bash, temos que torná-lo executável. Para fazer isso, execute o seguinte:

      • chmod u+x processNodejsImage.sh

      Isso dará ao seu usuário atual a permissão para executar o arquivo.

      Com nosso script no lugar, podemos escrever um novo módulo Node.js para executá-lo. Este script usará o execFile() para executar o script em um processo filho, capturando quaisquer erros e exibindo toda a saída no console.

      Em seu terminal, crie um novo arquivo JavaScript chamado getNodejsImage.js:

      Digite o código a seguir no editor de texto:

      ~/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}`);
      });
      

      Usamos o destructuring do JavaScript para importar a função execFile() a partir do módulo child_process. Em seguida, usamos essa função, passando o caminho do arquivo como primeiro nome. __dirname contém o caminho do diretório do módulo em que ele está escrito. O Node.js fornece a variável __dirname a um módulo quando o módulo é executado. Ao usar __dirname, nosso script irá sempre encontrar o arquivo processNodejsImage.sh entre sistemas operacionais diferentes, independentemente de onde executamos o getNodejsImage.js. Observe que para a configuração atual de projeto, getNodejsImage.js e processNodejsImage.sh devem estar na mesma pasta.

      O segundo argumento é um callback com os parâmetros error, stdout e stderr. Assim como no exemplo anterior que usou o exec(), verificamos cada resultado possível do arquivo de script e fazemos o log deles no console.

      No editor de texto, salve este arquivo e saia do editor.

      No terminal, use o node para executar o módulo:

      A execução deste script produzirá uma saída como esta:

      Output

      stdout: PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNDQyLjQgMjcwLjkiPjxkZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0iYiIgeDE9IjE4MC43IiB5MT0iODAuNyIge ...

      Observe que truncamos a saída neste artigo devido ao grande tamanho.

      Antes do base64 codificar a imagem, o processNodejsImage.sh primeiramente faz o download dela. Verifique também se você baixou a imagem inspecionando o diretório atual.

      Execute listFiles.js para encontrar a lista atualizada de arquivos em nosso diretório:

      O script exibirá um conteúdo similar ao seguinte no 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

      Agora, executamos com sucesso o processNodejsImage.sh como um processo filho no Node.js usando a função execFile().

      As funções exec() e execFile() podem executar comandos no shell do sistema operacional em um processo filho do Node.js. O Node.js também fornece outro método com funcionalidade semelhante, o spawn(). A diferença é que, em vez de obter a saída dos comandos do shell de uma só vez, nós a obtemos em partes através de uma stream. Na próxima seção, usaremos o comando spawn() para criar um processo filho.

      A função spawn() executa um comando em um processo. Essa função retorna dados via stream API. Portanto, para obter a saída do processo filho, precisamos ouvir eventos de streams.

      As streams no Node.js são instâncias de emissores de eventos. Se você quiser aprender mais sobre como ouvir eventos e conceitos básicos para interagir com streams, leia nosso guia Using Event Emitters in Node.js.

      Frequentemente, é uma boa ideia escolher o spawn() em vez de exec() ou execFile() quando o comando que você deseja executar pode gerar uma grande quantidade de dados. Com um buffer, como usado por exec() e execFile(), todos os dados processados são armazenados na memória do computador. Para grandes quantidades de dados, isso pode degradar o desempenho do sistema. Com uma stream, os dados são processados e transferidos em pequenas partes. Portanto, você pode processar uma grande quantidade de dados sem usar muita memória a qualquer momento.

      Vamos ver como podemos usar o spawn() para criar um processo filho. Vamos escrever um novo módulo Node.js que cria um processo filho para executar o comando find. Usaremos o comando find para listar todos os arquivos no diretório atual.

      Crie um novo arquivo chamado findFiles.js:

      No editor de texto, comece chamando o comando spawn():

      ~/child-processes/findFiles.js

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

      Primeiro importamos a função spawn() a partir do módulo child_process. Em seguida, chamamos a função spawn() para criar um processo filho que executa o comando find. Mantemos a referência ao processo na variável child, que usaremos para ouvir os eventos transmitidos dela.

      O primeiro argumento no spawn() é o comando que será executado, neste caso, o find. O segundo argumento é um array que contém os argumentos para o comando executado. Neste caso, estamos dizendo ao Node.js para executar o comando find com o argumento ., fazendo com que o comando encontre todos os arquivos no diretório atual. O comando equivalente no terminal é find ..

      Com as funções exec() e execFile(), escrevemos os argumentos juntamente com o comando em uma string. No entanto, com spawn(), todos os argumentos para comandos devem ser inseridos no array. Isso ocorre porque o spawn(), ao contrário do exec() e do execFile(), não cria um novo shell antes de executar um processo. Para ter comandos com argumentos em uma string, você precisa do Node.js para criar um novo shell também.

      Vamos continuar nosso módulo adicionando ouvintes para a saída do comando. Adicione as linhas destacadas a seguir:

      ~/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}`);
      });
      

      Os comandos podem retornar dados tanto na stream stdout quanto na stream stderr. Portanto, você adicionou ouvintes para ambas. Você pode adicionar ouvintes chamando o método on() dos objetos de cada stream. O evento data das streams nos dá a saída do comando para essa stream. Sempre que obtemos dados em qualquer uma das streams, nós os registramos no console.

      Em seguida, ouvimos outros dois eventos: o evento error, se o comando falhar ao executar ou for interrompido, e o evento close, para quando o comando tiver terminado a execução, fechando assim a stream.

      No editor de texto, complete o módulo Node.js escrevendo as seguintes linhas destacadas:

      ~/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}`);
      });
      

      Para os eventos error e close, você configurou um ouvinte diretamente na variável child. Ao ouvir eventos error, se um erro ocorrer, o Node.js fornecerá um objeto Error. Neste caso, você registra a propriedade message do erro.

      Ao ouvir o evento close, o Node.js fornece o código de saída do comando. Um código de saída denota se o comando funcionou ou não. Quando um comando é executado sem erros, ele retorna o valor mais baixo possível para um código de saída: 0. Quando executado com um erro, ele retorna um código não-zero.

      O módulo está completo. Salve e saia do nano com CTRL+X.

      Agora, execute o código com o comando node:

      Depois de terminar, você encontrará a seguinte saída:

      Output

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

      Encontramos uma lista de todos os arquivos em nosso diretório atual e o código de saída do comando, que é 0, uma vez que ele executou com sucesso. Embora nosso diretório atual tenha um pequeno número de arquivos, se executássemos esse código em nosso diretório home, o programa listaria todos os arquivos em cada pasta acessível ao usuário. Como ele tem uma saída potencialmente grande, usar a função spawn() é ideal, pois suas streams não exigem tanta memória quanto um buffer grande.

      Até agora, usamos funções para criar processos filhos para executar comandos externos no sistema operacional. O Node.js também fornece uma maneira de criar um processo filho que executa outros programas do Node.js. Vamos usar a função fork() para criar um processo filho para um módulo Node.js na próxima seção.

      O Node.js fornece a função fork(), uma variação da função spawn(), para criar um processo filho que também é um processo Node.js. O principal benefício do uso do fork() sobre o spawn() ou exec() para criar um processo Node.js é que o fork() permite a comunicação entre o processo pai e o filho.

      Com o fork(), além de recuperar dados do processo filho, um processo pai pode enviar mensagens para o processo filho em execução. Da mesma forma, o processo filho pode enviar mensagens para o processo pai.

      Vamos ver um exemplo onde usar o fork() para criar um novo processo filho do Node.js pode melhorar o desempenho da nossa aplicação. Os programas Node.js são executados em um único processo. Portanto, as tarefas de uso intensivo de CPU, como iterar sobre loops grandes ou analisar arquivos JSON grandes impede a execução de outro código JavaScript. Para certas aplicações, essa não é uma opção viável. Se um servidor Web está bloqueado, então ele não pode processar nenhuma nova requisição de entrada até que o código que está bloqueando tenha concluído sua execução.

      Vamos ver isso na prática criando um servidor Web com dois endpoints. Um endpoint fará uma computação lenta que bloqueia o processo do Node.js. O outro endpoint irá retornar um objeto JSON dizendo hello.

      Primeiro, crie um novo arquivo chamado httpServer.js, que terá o código para nosso servidor HTTP:

      Vamos começar configurando o servidor HTTP. Isso envolve a importação do módulo http, criação de uma função de ouvinte de requisição, criação de um objeto server, e a escuta de requisições no objeto server. Se você quiser mergulhar mais profundamente na criação de servidores HTTP no Node.js ou se gostaria de uma atualização, leia nosso guia How To Create a Web Server in Node.js with the HTTP Module.

      Digite o código a seguir em seu editor de texto para configurar um servidor 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}`);
      });
      

      Este código configura um servidor HTTP que será executado em http://localhost:8000. Ele usa template literals para gerar dinamicamente essa URL.

      Em seguida, vamos escrever uma função intencionalmente lenta que conta em um loop 5 bilhões de vezes. Antes da função requestListener(), adicione o seguinte código:

      ~/child-processes/httpServer.js

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

      Isso usa a sintaxe da função de seta para criar um loop while que conta até 5000000000.

      Para completar este módulo, precisamos adicionar código à função requestListener(). Nossa função irá chamar a slowFunction() no subpath e retornar uma pequena mensagem JSON para o outro. Adicione o código a seguir ao módulo:

      ~/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"}`);
        }
      };
      ...
      

      Se o usuário chegar até o servidor no subpath /total, então executamos slowFunction(). Se somos acessados no subpath /hello, retornamos esta mensagem JSON: {"message":"hello"}.

      Salve e saia do arquivo pressionando CTRL+X.

      Para testar, execute este módulo de servidor com node:

      Quando o servidor iniciar, o console exibirá o seguinte:

      Output

      Server is running on http://localhost:8000

      Agora, para testar o desempenho do nosso módulo, abra dois terminais adicionais. No primeiro terminal, use o comando curl para fazer uma requisição para o endpoint, /total, que esperamos ser lento:

      • curl http://localhost:8000/total

      No outro terminal, use o curl para fazer uma requisição para o endpoint /hello como esta:

      • curl http://localhost:8000/hello

      A primeira requisição retornará o seguinte JSON:

      Output

      {"totalCount":5000000000}

      A segunda requisição irá retornar este JSON:

      Output

      {"message":"hello"}

      A requisição para /hello finaliza apenas após a requisição para /total. O slowFunction() bloqueou a execução de todos os outros códigos enquanto ainda estava em seu loop. Verifique isso olhando para a saída do servidor Node.js que foi registrada em seu terminal original:

      Output

      Returning /total results Returning /hello results

      Para processarmos o código que está bloqueando ao mesmo tempo em que aceitamos requisições de entrada, podemos mover o código bloqueante para um processo filho com fork(). Vamos mover o código bloqueante para seu próprio módulo. O servidor Node.js então criará um processo filho quando alguém acessar o endpoint /total e ouvir os resultados deste processo filho.

      Refatore o servidor criando primeiro um novo módulo chamado getCount.js que conterá slowFunction():

      Agora, digite o código para slowFunction() mais uma vez:

      ~/child-processes/getCount.js

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

      Como este módulo será um processo filho criado com fork(), também podemos adicionar código para se comunicar com o processo pai quando slowFunction() tiver finalizado o processamento. Adicione o bloco de código a seguir que envia uma mensagem para o processo pai com o JSON para retornar ao usuário:

      ~/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);
        }
      });
      

      Vamos quebrar esse bloco de código. As mensagens entre um processo pai e o filho criado pelo fork() são acessíveis através do objeto process global do Node.js. Adicionamos um ouvinte à variável process para procurar eventos message. Após receber um evento message , verificamos se ele é o evento START. Nosso código de servidor enviará o evento START quando alguém acessar o endpoint /total. Ao receber esse evento, executamos slowFunction() e criamos uma string JSON com o resultado da função. Usamos process.send() para enviar uma mensagem para o processo pai.

      Salve e saia do getCount.js pressionando CTRL+X no nano.

      Agora, vamos modificar o arquivo httpServer.js para que, ao invés de chamar slowFunction(), ele crie um processo filho que executa getCount.js.

      Abra novamente httpServer.js com o nano:

      Primeiro, importe a função fork() a partir do módulo child_process:

      ~/child-processes/httpServer.js

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

      Em seguida, vamos remover o slowFunction() deste módulo e modificar a função requestListener() para criar um processo filho. Mude o código em seu arquivo para que fique assim:

      ~/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"}`);
        }
      };
      ...
      

      Quando alguém vai até o endpoint /total, agora criamos um novo processo filho com fork(). O argumento do fork() é o caminho para o módulo Node.js. Neste caso, ele é o arquivo getCount.js em nosso diretório atual, que recebemos a partir de __dirname. A referência a este processo filho é armazenada em uma variável. child.

      Em seguida, adicionamos um ouvinte ao objeto child. Este ouvinte captura todas as mensagens que o processo filho nos dá. Neste caso, getCount.js retornará uma string JSON com o número total contado pelo loop while. Quando recebemos essa mensagem, enviamos o JSON para o usuário.

      Usamos a função send() da variável child para exibir uma mensagem. Este programa envia a mensagem START, que começa a execução de slowFunction() no processo filho.

      Salve e saia do nano pressionando CTRL+X.

      Para testar a melhoria usando o fork() feito no servidor HTTP, comece executando o arquivo httpServer.js com node:

      Como antes, ele irá exibir a seguinte mensagem quando for iniciado:

      Output

      Server is running on http://localhost:8000

      Para testar o servidor, vamos precisar de dois terminais adicionais como fizemos da primeira vez. Você pode reutilizá-los se eles ainda estiverem abertos.

      No primeiro terminal, use o comando curl para fazer uma requisição para o endpoint, /total, que leva um tempo para ser calculado:

      • curl http://localhost:8000/total

      No outro terminal, use o curl para fazer uma requisição para o endpoint /hello, que responde em pouco tempo:

      • curl http://localhost:8000/hello

      A primeira requisição retornará o seguinte JSON:

      Output

      {"totalCount":5000000000}

      A segunda requisição retornará este JSON:

      Output

      {"message":"hello"}

      Ao contrário da primeira vez que tentamos isso, a segunda requisição para /hello executa imediatamente. Você pode confirmar revisando os logs, que se parecerão com este:

      Output

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

      Esses logs mostram que a requisição para o endpoint /hello foi executada após o processo filho ser criado, mas antes que o processo filho tivesse terminado sua tarefa.

      Uma vez que movemos o código bloqueante para um processo filho usando fork(), o servidor ainda foi capaz de responder a outras requisições e executar outro código JavaScript. Devido à capacidade de passagem de mensagem da função fork(), podemos controlar quando um processo filho começa uma atividade e retornar dados de um processo filho para um processo pai.

      Conclusão

      Neste artigo, você usou várias funções para criar um processo filho no Node.js. Primeiramente você criou processos filhos com exec() para executar comandos do shell a partir do código Node.js. Em seguida, você executou um arquivo executável com a função execFile(). Você viu a função spawn(), que também pode executar comandos mas retorna dados através de uma stream e não inicia um shell como fazem exec() e execFile(). Finalmente, você usou a função fork() para permitir uma comunicação bidirecional entre o processo pai e o filho.

      Para aprender mais sobre o módulo child_process, leia a documentação do Node.js. Se quiser continuar aprendendo sobre o Node.js, retorne para a série Como programar em Node.js, ou pesquise por projetos de programação e configurações em nossa página de tópicos do Node.



      Source link