
Node.js がシングルスレッドのイベント駆動型の非同期 I/O モデルを使用していることは誰もが知っていますが、その特性により、マルチコア CPU を活用できず、一部の非 I/O 操作を完了するのが苦手です (スクリプトの実行、AI コンピューティング、画像処理など) を解決するために、Node.js は従来のマルチプロセス (スレッド) ソリューションを提供します (プロセスとスレッドに関する議論については、著者の説明を参照してください)。他の記事 Node.js と同時実行モデル )、この記事では Node.js のマルチスレッド メカニズムを紹介します。
child_processモジュールを使用して、いくつかの特別なタスク (スクリプトの実行など) を完了するために Node.js の子プロセスを作成できます。このモジュールは主にexec 、 execFile 、 fork 、 spwanおよびその他のメソッドを提供します。 。 使用。
const { exec } = require('child_process');
exec('ls -al', (エラー、標準出力、標準エラー出力) => {
console.log(標準出力);
このメソッドは、
options.shellで指定された実行可能ファイルに従ってコマンド文字列を処理し、コマンドの実行中にその出力をキャッシュし、コマンドの実行が完了するまでコールバック関数のパラメーターの形式で実行結果を返します。
このメソッドのパラメータは次のように説明されます:
command : 実行されるコマンド ( ls -alなど);
options : パラメータ設定 (オプション)、関連するプロパティは次のとおりです:
cwd : 子プロセスの現在の作業ディレクトリ、デフォルトはprocess.cwd()値です;
shell
envデフォルト値はprocess.envの値です;
encoding : 文字エンコーディング、デフォルト値は次のとおりです: utf8 ;
コマンド文字列を処理するファイルUnixのデフォルト値は/bin/sh 、 Windowsのデフォルト値はprocess.env.ComSpecの値です (空の場合はcmd.exeです)。例:
const { exec 。 = require('child_process');
exec("print('Hello World!')", { シェル: 'python' }, (error, stdout, stderr) => {
console.log(標準出力);
});上記の例を実行すると、 Hello World!が出力されます。これはpython -c "print('Hello World!')"コマンドを実行するサブプロセスと同等です。そのため、この属性を使用する場合は、次の点に注意する必要があります。指定された実行可能ファイルは、 -cオプションによる関連ステートメントの実行をサポートする必要があります。
注: Node.js -cオプションもサポートしていますが、これは--checkオプションと同等であり、指定されたスクリプトに構文エラーがあるかどうかを検出するためにのみ使用され、関連するスクリプトは実行されません。
signal : 指定された AbortSignal を使用して子プロセスを終了します。この属性は v14.17.0 以降で使用できます。例:
const { exec } = require('child_process');
const ac = new AbortController();
exec('ls -al', { signal: ac.signal }, (error, stdout, stderr) => {});上記の例では、 ac.abort()を呼び出すことで子プロセスを早期に終了できます。
timeout : 子プロセスのタイムアウト時間 (この属性の値が0より大きい場合、子プロセスの実行時間が指定された値を超えると、属性killSignalで指定された終了シグナルが子プロセスに送信されます) )、デフォルト値は0です。
maxBuffer : stdout または stderr で許可される最大キャッシュ (バイナリ) を超えると、子プロセスが強制終了され、出力は1024 * 1024切り捨てられます
killSignal : 子プロセスの終了シグナル、デフォルト値はSIGTERM ;
uid : 子プロセスを実行するためのuid ;
gid : 子プロセスを実行するためのgid ;
windowsHide : Windowsシステムで一般的に使用される、子プロセスのコンソール ウィンドウを非表示にするかどうかデフォルト値はfalseです;
callback : error 、 stdout 、 stderrを含むコールバック関数 パラメータ:
error : コマンドラインが正常に実行された場合、値はnull 、それ以外の場合、値は Error のインスタンスであり、 error.codeが終了します。子プロセスのエラー コード、 error.signalは子プロセスの終了のシグナルです。stdoutおよびstderr : child encoding encodingがbufferの場合、プロセスのstdoutおよびstderrエンコードされます。 、またはstdoutまたはstderrの値が認識できない文字列である場合は、 bufferに従ってエンコードされます。const { execFile } = require('child_process');
execFile('ls', ['-al'], (error, stdout, stderr) => {
console.log(標準出力);
このメソッドの機能は exec と似ていますが、
execの違いは、 execFileデフォルトで指定された実行可能ファイル (つまり、パラメータfileの値) を使用してコマンドを直接処理するため、効率がexecよりわずかに高くなる点です。 (シェルの処理ロジックを見ると、効率は無視できるほど低いと感じます)。
このメソッドのパラメーターは次のように説明されます:
file : 実行可能ファイルの名前またはパス;
args : 実行可能ファイルのパラメーターのリスト;
shelloptions 。
false指定された実行可能ファイル (つまり、パラメータfileの値) を直接使用してコマンドを処理します。値がtrueまたはその他の文字列の場合、関数はexecのshellと同等です。デフォルト値はfalseです。maxBuffer gid cwd uid encoding timeout killSignal envwindowsVerbatimArguments Windowsするか。Unix Unix 、デフォルト値はfalseです。windowsHide 、およびsignalは上で紹介したので、ここでは繰り返しません。callback : コールバック関数execのcallbackと同等なので、ここでは説明しません。
const { fork } = require('child_process');
const echo = fork('./echo.js', {
サイレント: 本当
});
echo.stdout.on('データ', (データ) => {
console.log(`stdout: ${data}`);
});
echo.stderr.on('データ', (データ) => {
console.error(`stderr: ${data}`);
});
echo.on('close', (コード) => {
console.log(`子プロセスはコード ${code}` で終了しました);
このメソッドは、
指定された Node.js スクリプトを実行し、IPC 経由で親プロセスと通信するための新しい Node.js インスタンスを作成するために使用されます。
このメソッドのパラメータは次のように説明されます:
modulePath : 実行する Node.js スクリプトのパス;
args : Node.js スクリプトに渡されるパラメータのリスト;
options : パラメータの設定 (指定不可)、関連する属性例:
detached : spwanのoptions.detachedを参照;
execPath : 子プロセスの実行可能ファイルを作成します;
execArgv : 実行可能ファイルに渡される文字列パラメータのリスト、デフォルト値はprocess.execArgvの値です
serialization :インタープロセスのメッセージのシリアル番号タイプ利用可能な値はjsonでありadvanced json
stderr stdout stdin slient true falseを介して、親のプロセスのstdin 、 stdout 、 stderr継承さ
stdio options.stdio spwanここで注意する必要があるのは
slientの値を無視するipc [0, 1, 2, 'ipc']あることProperties cwd 、 env 、 uid 、 gid 、 windowsVerbatimArguments 、 signal 、 timeout 、 killSignalが上に導入されており、ここでは繰り返されません。
const { spawn } = require('child_process');
const ls = spawn('ls', ['-al']);
ls.stdout.on('データ', (データ) => {
console.log(`stdout: ${data}`);
});
ls.stderr.on('データ', (データ) => {
console.error(`stderr: ${data}`);
});
ls.on('close', (コード) => {
console.log( `code $ {code}`)で終了した子プロセス
このメソッドは、
child_processモジュールの基本メソッドです。 exec 、 execFile 、およびfork 、最終的にspawn呼び出して子プロセスを作成します。
このメソッドのパラメータは次のように説明されます:
command : 実行可能ファイルの名前またはパス;
args : 実行可能ファイルに渡されるパラメータのリスト;
options : パラメータの設定 (指定不可)、関連する属性は次のとおりです。
detached
argv0 command値です。
プロセスは実行され続けることができます)、デフォルト値はfalseであり、その値がtrueである場合、各プラットフォームは効果が次のとおりです。Windows
Windowsでは、親プロセスが終了した後、子プロセスが実行され続けることがあり、子プロセスWindowsでunref早期に終了することを望んでいる場合、次のポイントを同時に満たす必要があることに注意してください。
detached 。 trueに設定します。stdiostdio ignore 。たとえば、次の例:
// hello.js
const fs = require('fs');
インデックス = 0 とします。
関数 run() {
setTimeout(() => {
fs.writeFileSync('./hello', `index: ${index}`);
if (インデックス < 10) {
インデックス += 1;
走る();
}
}, 1000);
}
走る();
// メイン.js
const { spawn } = require('child_process');
const child = spawn('node', ['./hello.js'], {
切り離された: true、
標準オーディオ: '無視'
});
child.unref();stdio : 子プロセスの標準入出力構成、デフォルト値はpipe 、値は文字列または配列です。
pipe ['pipe', 'pipe', 'pipe']に変換されます)、値が配列の場合、使用可能な値は、 pipe 、 overlapped 、 ignore 、 inheritです。stdin 、 stdout 、 stderrの構成。項目の使用可能な値は、 pipe 、 overlapped 、 ignore 、 inherit 、 ipc 、Stream オブジェクト、正の整数 (親プロセスで開かれたファイル記述子)、 null (親プロセスで開かれたファイル記述子である場合) です。配列の最初の 3 つの項目にある場合は、 pipeと等価です。それ以外の場合は、 ignoreと等価です。)、 undefined (配列の最初の 3 つの項目にある場合は、 pipeと等価です。それ以外の場合は、と同等です。 ignore )。属性cwd 、 env 、 uid 、 gid 、 serialization 、 shell (値はbooleanまたはstring )、 windowsVerbatimArguments 、 windowsHide 、 signal 、 timeout 、 killSignalは上で紹介したので、ここでは繰り返しません。
上記は、 child_processモジュールの主なメソッドの使用法を簡単に説明したものです。 execSync 、 execFileSync 、 forkSync 、およびspwanSyncメソッドは、 exec 、 execFile 、およびspwanの同期バージョンであるため、パラメータに違いはありません。それらは繰り返されません。
cluster介して、node.jsプロセスクラスターを作成できます。プログラムの効率; 以下では、 clusterモジュールの使用方法を紹介します。
const http = require('http');
const クラスター = require('cluster');
const numCPUs = require('os').cpus().length;
if (cluster.isPrimary) {
for (let i = 0; i < numCPUs; i++) {
クラスター.フォーク();
}
} それ以外 {
http.createServer((req, res) => {
res.writeHead(200);
res.end(`${process.pid}n`);
}).listen(8000);
上記の例は、
cluster.isPrimary属性の判断(つまり、現在のプロセスがメインプロセスかどうかの判断)に基づいて2つの部分に分かれており、
cluster.fork呼び出しによる。8000 ) で待機します。上記の例を実行し、ブラウザでhttp://localhost:8000/アクセスすると、返されるpidアクセスごとに異なることがわかります。これは、リクエストが実際に各子プロセスに分散されていることを示しています。
node_cluster_sched_policy
NODE_CLUSTER_SCHED_POLICY cluster.schedulingPolicyプロパティを介して変更できるラウンドロビンスケジューリングです。
もう 1 つの注意点は、各子プロセスが HTTP サーバーを作成し、同じポートをリッスンしているにもかかわらず、これらの子プロセスが自由に競合できるわけではないということです
。
これは、すべての子プロセスの負荷が分散されることを保証できないためです。したがって、メイン プロセスがポートをリッスンし、分散ポリシーに従って処理するためにユーザー要求を特定のサブプロセスに転送するのが正しいプロセスである必要があります。
プロセスは相互に分離されているため、プロセスは通常、共有メモリ、メッセージ パッシング、パイプなどのメカニズムを通じて通信します。
Node.jsは
、次の例のように、消息传递を通じて親プロセスと子プロセス間の通信を完了します。
const cluster = require( 'cluster');
const numcpus = require( 'os')。cpus()。length;
if (cluster.isPrimary) {
for(i = 0; i <numcpus; i ++){
const worker = cluster.fork();
worker.on( 'message'、(message)=> {
console.log(`私はプライマリ(${process.pid})です。ワーカーからメッセージを受け取りました: "${message}"`);
worker.send( `労働者にメッセージを送信)
});
}
} それ以外 {
process.on( 'message'、(message)=> {
console.log(`私はワーカー(${process.pid})、プライマリからメッセージを受け取りました: "${message}"`)
});
http.createserver((req、res)=> {
res.writeHead(200);
res.End( `$ {process.pid} n`);
process.send( 'プライマリにメッセージを送信');
}).listen(8000);
}上記の例を実行してhttp://localhost:8000/にアクセスし、端末を確認します。出力が次のように表示されます。
私はプライマリ(44460)、ワーカーからメッセージを受け取りました。「プライマリにメッセージを送信」 私はワーカー(44461)です。プライマリから「ワーカーにメッセージを送信してください」というメッセージを受け取りました。 私はプライマリ (44460) です。ワーカーからメッセージを受け取りました: 「メッセージをプライマリに送信してください」 私はワーカー(44462)です。プライマリからメッセージを受け取りました: 「ワーカーにメッセージを送信」
このメカニズムを使用すると、各子プロセスのステータスを監視できるため、子プロセスで事故が発生したときに、適切なタイミングで介入できます。サービスの可用性を確保するため。
clusterモジュールのインターフェイスは非常にシンプルです。スペースを節約するために、ここでは、 cluster.setupPrimaryメソッドに関するいくつかの特別な説明のみを記載します。他のメソッドについては、公式ドキュメントを確認してください。cluster.setupPrimary
cluster.setupPrimary呼び出された後、関連する設定が行われます。は、 cluster.settings属性に同期され、各呼び出しは現在のcluster.settings属性の値に基づきます。cluster.setupPrimarycluster.setupPrimary呼び出された後は、実行中の子プロセスには影響せず、後続のcluster.fork呼び出しにのみ影響します。影響を受けます。cluster.setupPrimarycluster.setupPrimary呼び出された後は、 cluster.forkへの以降のパスには影響しません。cluster.setupPrimary 呼び出しのenvパラメーターはcluster.setupPrimaryプロセスでのみ使用できます。先ほどclusterモジュールを紹介しました。これにより、Node.js プロセス クラスターを作成してプログラムの実行効率を向上させることができます。ただし、 clusterマルチプロセス モデルに基づいており、プロセス間の切り替えと分離にコストがかかります。子プロセスの数が増加すると、システム リソースの制約により応答できなくなるという問題が発生しやすくなります。このような問題を解決するために、Node.js はworker_threads提供します。以下では、具体的な例を通してこのモジュールの使用法を簡単に紹介します
。
const http = require('http');
const { ワーカー } = require('worker_threads');
http.createServer((req, res) => {
const httpWorker = new Worker('./http_worker.js');
httpWorker.on('メッセージ', (結果) => {
res.writeHead(200);
res.end(`${結果}n`);
});
httpWorker.postMessage('トム');
}).listen(8000);
// http_worker.js
const {parentPort } = require('worker_threads');
parentPort.on('メッセージ', (名前) => {
ParentPort.PostMessage( `welcone $ {name}!`);
})上記の字符串JavaScript worker_threads.Worker
worker_threads worker_threads使用する場合、次の点に注意する必要があります。
たとえば、上記の例は次のように変更できます:
const code = "const {parentPort } = require('worker_threads');parentPort.on('message', (name) => {parentPort.postMessage(`Welcone ${名前}!` );})";
httpworker
worker_threads.Worker workerData
Worker(code、{eval:true});.js
const {worker} = require( 'worker_threads');
const httpWorker = new Worker('./http_worker.js', {workerData: { name: 'Tom'} });
// http_worker.js
const {workerData} = require('worker_threads');
const { Worker, SHARE_ENV worker_threads.Worker
=
require( 'worker_threads');
const worker = new Worker('process.env.SET_IN_WORKER = "foo"', { eval: true, env: SHARE_ENV });
worker.on( 'exit'、()=> {
console.log(process.env.set_in_worker);
clusterのプロセス間通信メカニズムとは異なり、 worker_threadsスレッド間の通信に MessageChannel を使用します。
parentPort.postMessageメソッドを通じてメイン スレッドにメッセージを送信し、 messageをリッスンすることでメイン スレッドからのメッセージを処理します。
parentPortメッセージのmessageイベント。postMessageメソッドを介してhttpWorker httpWorkerメッセージを送信し、以下のこのワーカーサブスレッドに置き換えられます)。 httpWorkerのmessageイベントをリッスンすることによって。Node.js では、 clusterによって作成された子プロセスであっても、 worker_threadsによって作成されたワーカー子スレッドであっても、それらはすべて独自の V8 インスタンスとイベント ループを持っています。違いは、
Worker サブスレッドは子プロセスより効率的であるように見えますが、Worker サブスレッドにも欠点があります。つまり、 cluster負荷分散を提供しますが、 worker_threadsは負荷分散の設計と実装を自分で完了する必要があるということです。
この記事では、これら3つのモジュールを介して3つのchild_process 、 cluster 、およびworker_threadsの使用を紹介します。スレッド)モード(AI、画像処理など)の動作効率。各モジュールには、この記事では、自分の問題に基づいて効率的に使用する必要があります。最後に、この記事に間違いがあるなら、私はあなたが彼らを毎日幸せにすることを願っています。