Node.jsで子プロセスを起動する方法

提供:Dev Guides
移動先:案内検索

著者はCOVID-19救済基金を選択し、 Write forDOnationsプログラムの一環として寄付を受け取りました。

序章

ユーザーが単一のNode.jsプログラムを実行すると、実行中のプログラムのインスタンスを表す単一のオペレーティングシステム(OS)processとして実行されます。 そのプロセス内で、Node.jsは単一のスレッドでプログラムを実行します。 このシリーズの前半でNode.jsチュートリアルで非同期コードを作成する方法で説明したように、1つのプロセスで実行できるスレッドは1つだけであるため、JavaScriptで実行するのに長い時間がかかる操作] Node.jsスレッドをブロックし、他のコードの実行を遅らせることができます。 この問題を回避するための重要な戦略は、子プロセス、または実行時間の長いタスクに直面したときに別のプロセスによって作成されたプロセスを起動することです。 新しいプロセスが起動されると、オペレーティングシステムはマルチプロセッシング技術を使用して、メインのNode.jsプロセスと追加の子プロセスが同時に実行されるようにすることができます。

Node.jsには、新しいプロセスを作成する関数を持つ child_processmoduleが含まれています。 このモジュールは、長時間実行されるタスクを処理するだけでなく、OSとインターフェイスして、shellコマンドを実行することもできます。 システム管理者は、Node.jsを使用してシェルコマンドを実行し、シェルスクリプトではなくNode.jsモジュールとして操作を構造化および維持できます。

このチュートリアルでは、一連のサンプルNode.jsアプリケーションを実行しながら子プロセスを作成します。 child_processモジュールを使用してプロセスを作成するには、 buffer を介して子プロセスの結果を取得するか、 exec()関数を使用して文字列を取得します。 spawn()関数を使用したデータストリーム。 最後に、 fork()を使用して、実行中に通信できる別のNode.jsプログラムの子プロセスを作成します。 これらの概念を説明するために、ディレクトリの内容を一覧表示するプログラム、ファイルを検索するプログラム、および複数のエンドポイントを持つWebサーバーを作成します。

前提条件

  • これらの例を実行するには、Node.jsがインストールされている必要があります。 このチュートリアルでは、バージョン10.22.0を使用します。 これをmacOSまたはUbuntu18.04にインストールするには、Node.jsをインストールしてmacOSにローカル開発環境を作成する方法またはのPPAを使用したインストール]セクションの手順に従います。 Ubuntu18.04にNode.jsをインストールする方法。
  • この記事では、Webサーバーを作成する例を使用して、fork()関数がどのように機能するかを説明します。 Webサーバーの作成に慣れるために、HTTPモジュールを使用してNode.jsでWebサーバーを作成する方法に関するガイドを読むことができます。

ステップ1—exec()を使用して子プロセスを作成する

開発者は通常、シェルのパイピングやリダイレクトを使用するなど、シェルを使用してNode.jsプログラムの出力を操作する必要がある場合に、オペレーティングシステムでコマンドを実行する子プロセスを作成します。 Node.jsのexec()関数は、新しいシェルプロセスを作成し、そのシェルでコマンドを実行します。 コマンドの出力はメモリ内のバッファに保持され、exec()に渡されるコールバック関数を介して受け入れることができます。

Node.jsで最初の子プロセスの作成を始めましょう。 まず、このチュートリアル全体で作成するスクリプトを保存するためのコーディング環境を設定する必要があります。 ターミナルで、child-processesというフォルダーを作成します。

mkdir child-processes

cdコマンドを使用して、ターミナルにそのフォルダを入力します。

cd child-processes

listFiles.jsという名前の新しいファイルを作成し、そのファイルをテキストエディタで開きます。 このチュートリアルでは、ターミナルテキストエディタであるnanoを使用します。

nano listFiles.js

exec()関数を使用してlsコマンドを実行するNode.jsモジュールを作成します。 lsコマンドは、ディレクトリ内のファイルとフォルダを一覧表示します。 このプログラムは、lsコマンドからの出力を取得し、ユーザーに表示します。

テキストエディタで、次のコードを追加します。

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

まず、 JavaScript destructuring を使用して、child_processモジュールからexec()コマンドをインポートします。 インポートしたら、exec()関数を使用します。 最初の引数は、実行したいコマンドです。 この場合は、ls -lhで、現在のディレクトリ内のすべてのファイルとフォルダが長い形式で一覧表示され、出力の上部に人間が読める形式の合計ファイルサイズが表示されます。

2番目の引数は、errorstdout、およびstderrの3つのパラメーターを持つコールバック関数です。 コマンドの実行に失敗した場合、errorはコマンドが失敗した理由をキャプチャします。 これは、実行しようとしているコマンドがシェルで見つからない場合に発生する可能性があります。 コマンドが正常に実行されると、標準出力ストリームに書き込むデータはすべてstdoutにキャプチャされ、標準エラーストリームに書き込むデータはすべてキャプチャされます。 stderrで。

注:errorstderrの違いに注意することが重要です。 コマンド自体の実行に失敗した場合、errorはエラーをキャプチャします。 コマンドが実行されても出力がエラーストリームに返される場合、stderrはそれをキャプチャします。 最も回復力のあるNode.jsプログラムは、子プロセスのすべての可能な出力を処理します。


コールバック関数では、最初にエラーを受け取ったかどうかを確認します。 その場合、エラーのmessageErrorオブジェクトのプロパティ)をconsole.error()で表示し、関数をreturnで終了します。 次に、コマンドがエラーメッセージを出力したかどうかを確認し、出力した場合はreturnを確認します。 コマンドが正常に実行されると、その出力がconsole.log()を使用してコンソールに記録されます。

このファイルを実行して、実際の動作を確認してみましょう。 まず、CTRL+Xを押して、nanoを保存して終了します。

ターミナルに戻り、nodeコマンドを使用してアプリケーションを実行します。

node listFiles.js

端末に次の出力が表示されます。

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

child-processesディレクトリの内容が長い形式で一覧表示され、上部に内容のサイズが表示されます。 結果には、sammyの代わりに独自のユーザーとグループが含まれます。 これは、listFiles.jsプログラムがシェルコマンドls -lhを正常に実行したことを示しています。

次に、並行プロセスを実行する別の方法を見てみましょう。 Node.jsのchild_processモジュールは、execFile()関数を使用して実行可能ファイルを実行することもできます。 execFile()関数とexec()関数の主な違いは、execFile()の最初の引数が、コマンドではなく実行可能ファイルへのパスになったことです。 実行可能ファイルの出力は、exec()のようなバッファーに格納され、errorstdout、およびstderrパラメーターを使用してコールバック関数を介してアクセスします。

注:.bat.cmdファイルなどのWindowsのスクリプトは、ファイルの実行時に関数がシェルを作成しないため、execFile()では実行できません。 Unix、Linux、およびmacOSでは、実行可能スクリプトを実行するために必ずしもシェルが必要なわけではありません。 ただし、Windowsマシンには、スクリプトを実行するためのシェルが必要です。 Windowsでスクリプトファイルを実行するには、exec()を使用します。これにより、新しいシェルが作成されます。 または、spawn()を使用することもできます。これは、このステップの後半で使用します。

ただし、execFile()を使用すると、Windowsで.exeファイルを正常に実行できることに注意してください。 この制限は、実行にシェルを必要とするスクリプトファイルにのみ適用されます。


execFile()を実行するための実行可能スクリプトを追加することから始めましょう。 Node.jsWebサイトからNode.jsロゴをダウンロードするbashスクリプトを記述し、Base64がそれをエンコードしてデータを次の文字列に変換します。 ASCII文字。

processNodejsImage.shという名前の新しいシェルスクリプトファイルを作成します。

nano processNodejsImage.sh

次に、画像をダウンロードしてbase64で変換するスクリプトを記述します。

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

最初のステートメントはshebangステートメントです。 これは、Unix、Linux、およびmacOSで、スクリプトを実行するためのシェルを指定する場合に使用されます。 2番目のステートメントはcurlコマンドです。 コマンドがcurlであるcURLユーティリティは、サーバーとの間でデータを転送できるコマンドラインツールです。 cURLを使用してWebサイトからNode.jsロゴをダウンロードし、リダイレクトを使用してダウンロードしたデータを新しいファイルnodejs-logo.svgに保存します。 最後のステートメントは、base64ユーティリティを使用して、cURLでダウンロードしたnodejs-logo.svgファイルをエンコードします。 次に、スクリプトはエンコードされた文字列をコンソールに出力します。

続行する前に保存して終了します。

Nodeプログラムでbashスクリプトを実行するには、スクリプトを実行可能にする必要があります。 これを行うには、次を実行します。

chmod u+x processNodejsImage.sh

これにより、現在のユーザーにファイルを実行する権限が与えられます。

スクリプトを配置したら、それを実行するための新しいNode.jsモジュールを作成できます。 このスクリプトは、execFile()を使用して子プロセスでスクリプトを実行し、エラーをキャッチしてすべての出力をコンソールに表示します。

ターミナルで、getNodejsImage.jsという名前の新しいJavaScriptファイルを作成します。

nano getNodejsImage.js

テキストエディタに次のコードを入力します。

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

JavaScript分解を使用して、child_processモジュールからexecFile()関数をインポートします。 次に、その関数を使用して、ファイルパスを名として渡します。 __dirnameには、それが書き込まれているモジュールのディレクトリパスが含まれています。 Node.jsは、モジュールの実行時に__dirname変数をモジュールに提供します。 __dirnameを使用することにより、getNodejsImage.jsを実行する場所に関係なく、スクリプトは常に異なるオペレーティングシステム間でprocessNodejsImage.shファイルを検索します。 現在のプロジェクト設定では、getNodejsImage.jsprocessNodejsImage.shが同じフォルダーにある必要があることに注意してください。

2番目の引数は、errorstdout、およびstderrパラメーターを使用したコールバックです。 exec()を使用した前の例と同様に、スクリプトファイルの可能な出力をそれぞれチェックし、コンソールに記録します。

テキストエディタで、このファイルを保存してエディタを終了します。

端末で、nodeを使用してモジュールを実行します。

node getNodejsImage.js

このスクリプトを実行すると、次のような出力が生成されます。

Outputstdout:
PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNDQyLjQgMjcwLjkiPjxkZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0iYiIgeDE9IjE4MC43IiB5MT0iODAuNyIge
...

サイズが大きいため、この記事では出力を切り捨てたことに注意してください。

画像をbase64でエンコードする前に、processNodejsImage.shが最初に画像をダウンロードします。 現在のディレクトリを調べて、イメージをダウンロードしたことを確認することもできます。

listFiles.jsを実行して、ディレクトリ内の更新されたファイルのリストを見つけます。

node listFiles.js

スクリプトは、ターミナルに次のようなコンテンツを表示します。

Outputstdout:
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

これで、execFile()関数を使用して、Node.jsで子プロセスとしてprocessNodejsImage.shを正常に実行できました。

exec()およびexecFile()関数は、Node.js子プロセスのオペレーティングシステムのシェルでコマンドを実行できます。 Node.jsは、同様の機能を備えた別のメソッドspawn()も提供します。 違いは、シェルコマンドの出力を一度に取得するのではなく、ストリームを介してチャンクで取得することです。 次のセクションでは、spawn()コマンドを使用して子プロセスを作成します。

ステップ2—spawn()を使用して子プロセスを作成する

spawn()関数は、プロセスでコマンドを実行します。 この関数は、ストリームAPIを介してデータを返します。 したがって、子プロセスの出力を取得するには、ストリームイベントをリッスンする必要があります。

Node.jsのストリームは、イベントエミッターのインスタンスです。 イベントのリッスンとストリームとの対話の基礎について詳しく知りたい場合は、Node.jsでのイベントエミッターの使用に関するガイドをお読みください。

実行したいコマンドが大量のデータを出力する可能性がある場合は、exec()またはexecFile()ではなくspawn()を選択することをお勧めします。 exec()およびexecFile()で使用されるバッファーを使用すると、処理されたすべてのデータがコンピューターのメモリーに保管されます。 大量のデータの場合、これによりシステムパフォーマンスが低下する可能性があります。 ストリームを使用すると、データは小さなチャンクで処理および転送されます。 したがって、一度に多くのメモリを使用することなく、大量のデータを処理できます。

spawn()を使用して子プロセスを作成する方法を見てみましょう。 findコマンドを実行するための子プロセスを作成する新しいNode.jsモジュールを作成します。 findコマンドを使用して、現在のディレクトリ内のすべてのファイルを一覧表示します。

findFiles.jsという名前の新しいファイルを作成します。

nano findFiles.js

テキストエディタで、spawn()コマンドを呼び出すことから始めます。

〜/ child-processes / findFiles.js

const { spawn } = require('child_process');

const child = spawn('find', ['.']);

最初に、spawn()関数をchild_processモジュールからインポートしました。 次に、spawn()関数を呼び出して、findコマンドを実行する子プロセスを作成しました。 プロセスへの参照をchild変数に保持します。この変数を使用して、ストリーミングされたイベントをリッスンします。

spawn()の最初の引数は、実行するコマンドです。この場合はfindです。 2番目の引数は、実行されたコマンドの引数を含むarrayです。 この場合、引数.を指定してfindコマンドを実行するようにNode.jsに指示します。これにより、コマンドは現在のディレクトリ内のすべてのファイルを検索します。 ターミナルでの同等のコマンドはfind .です。

exec()関数とexecFile()関数を使用して、コマンドとともに引数を1つの文字列に記述しました。 ただし、spawn()では、コマンドへのすべての引数を配列に入力する必要があります。 これは、spawn()は、exec()execFile()とは異なり、プロセスを実行する前に新しいシェルを作成しないためです。 引数を1つの文字列にまとめたコマンドを使用するには、Node.jsで新しいシェルも作成する必要があります。

コマンドの出力にリスナーを追加して、モジュールを続けましょう。 次の強調表示された行を追加します。

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

コマンドはstdoutストリームまたはstderrストリームのいずれかでデータを返すことができるため、両方のリスナーを追加しました。 各ストリームのオブジェクトのon()メソッドを呼び出すことにより、リスナーを追加できます。 ストリームからのdataイベントは、そのストリームへのコマンドの出力を提供します。 いずれかのストリームでデータを取得するたびに、それをコンソールに記録します。

次に、他の2つのイベントをリッスンします。コマンドの実行に失敗または中断された場合のerrorイベントと、コマンドの実行が終了してストリームを閉じる場合のcloseイベントです。

テキストエディタで、次の強調表示された行を記述してNode.jsモジュールを完成させます。

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

errorおよびcloseイベントの場合、child変数に直接リスナーを設定します。 errorイベントをリッスンするときに、イベントが発生すると、Node.jsはErrorオブジェクトを提供します。 この場合、エラーのmessageプロパティをログに記録します。

closeイベントをリッスンするとき、Node.jsはコマンドの終了コードを提供します。 終了コードは、コマンドが正常に実行されたかどうかを示します。 コマンドがエラーなしで実行されると、終了コードの可能な最小値0が返されます。 エラーで実行すると、ゼロ以外のコードが返されます。

モジュールが完成しました。 CTRL+Xを使用してnanoを保存して終了します。

次に、nodeコマンドを使用してコードを実行します。

node findFiles.js

完了すると、次の出力が表示されます。

Outputstdout:
.
./findFiles.js
./listFiles.js
./nodejs-logo.svg
./processNodejsImage.sh
./getNodejsImage.js

child process exited with code 0

現在のディレクトリにあるすべてのファイルのリストと、コマンドの終了コード(0)が正常に実行されたことがわかります。 現在のディレクトリには少数のファイルがありますが、このコードをホームディレクトリで実行すると、プログラムはユーザーがアクセスできるすべてのフォルダにあるすべてのファイルを一覧表示します。 出力が非常に大きくなる可能性があるため、spawn()関数を使用すると、ストリームが大きなバッファーほど多くのメモリを必要としないため、最も理想的です。

これまで、関数を使用して子プロセスを作成し、オペレーティングシステムで外部コマンドを実行してきました。 Node.jsは、他のNode.jsプログラムを実行する子プロセスを作成する方法も提供します。 次のセクションでは、fork()関数を使用して、Node.jsモジュールの子プロセスを作成しましょう。

ステップ3—fork()を使用して子プロセスを作成する

Node.jsは、spawn()のバリエーションであるfork()関数を提供して、Node.jsプロセスでもある子プロセスを作成します。 fork()を使用してspawn()またはexec()上にNode.jsプロセスを作成する主な利点は、fork()が親プロセスと子プロセス間の通信を可能にすることです。

fork()を使用すると、子プロセスからデータを取得することに加えて、親プロセスは実行中の子プロセスにメッセージを送信できます。 同様に、子プロセスは親プロセスにメッセージを送信できます。

fork()を使用して新しいNode.js子プロセスを作成すると、アプリケーションのパフォーマンスが向上する例を見てみましょう。 Node.jsプログラムは単一のプロセスで実行されます。 したがって、大きなループの反復や大きな JSONファイルの解析などのCPUを集中的に使用するタスクは、他のJavaScriptコードの実行を停止します。 特定のアプリケーションでは、これは実行可能なオプションではありません。 Webサーバーがブロックされている場合、ブロックコードが実行を完了するまで、新しい着信要求を処理できません。

2つのエンドポイントを持つWebサーバーを作成して、これを実際に見てみましょう。 1つのエンドポイントは、Node.jsプロセスをブロックする低速の計算を実行します。 もう一方のエンドポイントは、helloというJSONオブジェクトを返します。

まず、httpServer.jsという名前の新しいファイルを作成します。このファイルには、HTTPサーバーのコードが含まれています。

nano httpServer.js

まず、HTTPサーバーを設定します。 これには、httpモジュールのインポート、要求リスナー関数の作成、サーバーオブジェクトの作成、およびサーバーオブジェクトでの要求のリッスンが含まれます。 Node.jsでのHTTPサーバーの作成についてさらに詳しく知りたい場合、または復習が必要な場合は、HTTPモジュールを使用してNode.jsでWebサーバーを作成する方法に関するガイドをお読みください。

テキストエディタに次のコードを入力して、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}`);
});

このコードは、http://localhost:8000で実行されるHTTPサーバーをセットアップします。 テンプレートリテラルを使用して、そのURLを動的に生成します。

次に、50億回ループでカウントする意図的に遅い関数を記述します。 requestListener()関数の前に、次のコードを追加します。

〜/ child-processes / httpServer.js

...
const port = 8000;

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

  return counter;
}

const requestListener = function (req, res) {};
...

これは、矢印関数構文を使用して、5000000000にカウントされるwhileループを作成します。

このモジュールを完了するには、requestListener()関数にコードを追加する必要があります。 この関数は、サブパスでslowFunction()を呼び出し、もう一方に小さなJSONメッセージを返します。 次のコードをモジュールに追加します。

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

ユーザーが/totalサブパスでサーバーに到達した場合、slowFunction()を実行します。 /helloサブパスでヒットした場合、次のJSONメッセージを返します:{"message":"hello"}

CTRL+Xを押して、ファイルを保存して終了します。

テストするには、このサーバーモジュールをnodeで実行します。

node httpServer.js

サーバーが起動すると、コンソールに次のように表示されます。

OutputServer is running on http://localhost:8000

ここで、モジュールのパフォーマンスをテストするために、2つの追加の端子を開きます。 最初の端末で、curlコマンドを使用して、/totalエンドポイントにリクエストを送信します。これは遅いと予想されます。

curl http://localhost:8000/total

もう一方の端末では、curlを使用して、次のように/helloエンドポイントにリクエストを送信します。

curl http://localhost:8000/hello

最初のリクエストは次のJSONを返します。

Output{"totalCount":5000000000}

2番目のリクエストはこのJSONを返しますが:

Output{"message":"hello"}

/helloへのリクエストは、/totalへのリクエスト後にのみ完了しました。 slowFunction()は、ループ内にある間、他のすべてのコードの実行をブロックしました。 これは、元の端末に記録されたNode.jsサーバーの出力を確認することで確認できます。

OutputReturning /total results
Returning /hello results

着信リクエストを受け入れながらブロッキングコードを処理するには、fork()を使用してブロッキングコードを子プロセスに移動します。 ブロッキングコードを独自のモジュールに移動します。 Node.jsサーバーは、誰かが/totalエンドポイントにアクセスし、この子プロセスからの結果をリッスンすると、子プロセスを作成します。

最初にslowFunction()を含むgetCount.jsという新しいモジュールを作成して、サーバーをリファクタリングします。

nano getCount.js

ここで、slowFunction()のコードをもう一度入力します。

〜/ child-processes / getCount.js

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

  return counter;
}

このモジュールはfork()で作成された子プロセスになるため、slowFunction()の処理が完了したときに親プロセスと通信するコードを追加することもできます。 ユーザーに返すJSONを使用して親プロセスにメッセージを送信する次のコードブロックを追加します。

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

このコードブロックを分解してみましょう。 fork()によって作成された親プロセスと子プロセスの間のメッセージには、Node.jsグローバルプロセスオブジェクトを介してアクセスできます。 process変数にリスナーを追加して、messageイベントを探します。 messageイベントを受信したら、それがSTARTイベントであるかどうかを確認します。 サーバーコードは、誰かが/totalエンドポイントにアクセスすると、STARTイベントを送信します。 そのイベントを受信すると、slowFunction()を実行し、関数の結果を使用してJSON文字列を作成します。 process.send()を使用して、親プロセスにメッセージを送信します。

nanoでCTRL+Xと入力して、getCount.jsを保存して終了します。

次に、httpServer.jsファイルを変更して、slowFunction()を呼び出す代わりに、getCount.jsを実行する子プロセスを作成するようにします。

httpServer.jsnanoで再度開きます。

nano httpServer.js

まず、child_processモジュールからfork()関数をインポートします。

〜/ child-processes / httpServer.js

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

次に、このモジュールからslowFunction()を削除し、requestListener()関数を変更して子プロセスを作成します。 次のようにファイルのコードを変更します。

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

誰かが/totalエンドポイントに移動すると、fork()を使用して新しい子プロセスが作成されます。 fork()の引数は、Node.jsモジュールへのパスです。 この場合、それは__dirnameから受け取る現在のディレクトリのgetCount.jsファイルです。 この子プロセスへの参照は、変数childに格納されます。

次に、childオブジェクトにリスナーを追加します。 このリスナーは、子プロセスが提供するメッセージをキャプチャします。 この場合、getCount.jsは、whileループによってカウントされた総数を含むJSON文字列を返します。 そのメッセージを受信すると、JSONをユーザーに送信します。

child変数のsend()関数を使用して、メッセージを送信します。 このプログラムはメッセージSTARTを送信し、子プロセスでslowFunction()の実行を開始します。

CTRL+Xと入力して、nanoを保存して終了します。

HTTPサーバーで行われたfork()を使用して改善をテストするには、nodehttpServer.jsファイルを実行することから始めます。

node httpServer.js

以前と同様に、起動時に次のメッセージが出力されます。

OutputServer is running on http://localhost:8000

サーバーをテストするには、最初に行ったように、追加の2つの端末が必要になります。 それらがまだ開いている場合は、それらを再利用できます。

最初の端末で、curlコマンドを使用して、/totalエンドポイントにリクエストを送信します。これには、計算に時間がかかります。

curl http://localhost:8000/total

もう一方の端末では、curlを使用して、/helloエンドポイントにリクエストを送信します。これにより、短時間で応答します。

curl http://localhost:8000/hello

最初のリクエストは次のJSONを返します。

Output{"totalCount":5000000000}

2番目のリクエストはこのJSONを返しますが:

Output{"message":"hello"}

これを最初に試したときとは異なり、/helloへの2番目の要求はすぐに実行されます。 次のようなログを確認することで確認できます。

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

これらのログは、/helloエンドポイントの要求が、子プロセスが作成された後、子プロセスがタスクを完了する前に実行されたことを示しています。

fork()を使用して子プロセスでブロッキングコードを移動したため、サーバーは他のリクエストに応答し、他のJavaScriptコードを実行することができました。 fork()関数のメッセージパッシング機能により、子プロセスがアクティビティを開始するタイミングを制御したり、子プロセスから親プロセスにデータを返したりすることができます。

結論

この記事では、さまざまな関数を使用してNode.jsで子プロセスを作成しました。 最初にexec()を使用して子プロセスを作成し、Node.jsコードからシェルコマンドを実行しました。 次に、execFile()関数を使用して実行可能ファイルを実行しました。 spawn()関数を見ました。この関数はコマンドを実行することもできますが、ストリームを介してデータを返し、exec()execFile()のようなシェルを開始しません。 最後に、fork()関数を使用して、親プロセスと子プロセス間の双方向通信を可能にしました。

child_processモジュールの詳細については、Node.jsのドキュメントをご覧ください。 Node.jsの学習を継続したい場合は、 Node.jsシリーズのコーディング方法に戻るか、Nodeトピックページでプログラミングプロジェクトとセットアップを参照してください。