Node.jsで子プロセスを起動する方法
著者は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番目の引数は、error
、stdout
、およびstderr
の3つのパラメーターを持つコールバック関数です。 コマンドの実行に失敗した場合、error
はコマンドが失敗した理由をキャプチャします。 これは、実行しようとしているコマンドがシェルで見つからない場合に発生する可能性があります。 コマンドが正常に実行されると、標準出力ストリームに書き込むデータはすべてstdout
にキャプチャされ、標準エラーストリームに書き込むデータはすべてキャプチャされます。 stderr
で。
注:error
とstderr
の違いに注意することが重要です。 コマンド自体の実行に失敗した場合、error
はエラーをキャプチャします。 コマンドが実行されても出力がエラーストリームに返される場合、stderr
はそれをキャプチャします。 最も回復力のあるNode.jsプログラムは、子プロセスのすべての可能な出力を処理します。
コールバック関数では、最初にエラーを受け取ったかどうかを確認します。 その場合、エラーのmessage
(Error
オブジェクトのプロパティ)を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()
のようなバッファーに格納され、error
、stdout
、および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.js
とprocessNodejsImage.sh
が同じフォルダーにある必要があることに注意してください。
2番目の引数は、error
、stdout
、および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.js
をnano
で再度開きます。
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()
を使用して改善をテストするには、node
でhttpServer.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トピックページでプログラミングプロジェクトとセットアップを参照してください。