ホームページ  >  記事  >  ウェブフロントエンド  >  Node.js で子プロセスを作成する方法の詳細な分析

Node.js で子プロセスを作成する方法の詳細な分析

青灯夜游
青灯夜游転載
2021-10-12 10:05:482904ブラウズ

この記事では、Node.js のサブプロセスを理解し、Node.js でサブプロセスを作成する 4 つの方法を紹介します。皆様のお役に立てれば幸いです。

Node.js で子プロセスを作成する方法の詳細な分析

ご存知のとおり、Node.js はシングルスレッド、非同期、ノンブロッキングのプログラミング言語です。 CPU?これには、子プロセスを作成するための child_process モジュールが必要です。Node.js では、子プロセスを作成するには 4 つの方法があります:

  • exec

  • execFile

  • ##spawn

  • fork

[推奨学習: "

nodejs チュートリアル "]

上記 4 つのメソッドは

ChildProcess を返します インスタンス (EventEmitter から継承)。これには 3 つの標準 stdio ストリームがあります:

  • child.stdin

  • child.stdout

  • ##child.stderr

  • イベント

exit

: 子プロセスの終了時にトリガーされ、パラメータはコード エラー コードとシグナル割り込みシグナルです。

close

: 子プロセスが終了し、stdio ストリームが閉じられるとトリガーされます。パラメータは exit イベントと同じです。

disconnect

: 親プロセスが child.disconnect() を呼び出すか、子プロセスが process.disconnect() を呼び出すとトリガーされます。

error

: 子プロセスを作成できない場合、強制終了できない場合、または子プロセスへのメッセージの送信に失敗した場合にトリガーされます。

message

: 子プロセスが process.send() を通じてメッセージを送信するとトリガーされます。

spawn

: 子プロセスが正常に作成されたときにトリガーされます (このイベントは Node.js v15.1 でのみ追加されました)。

exec

メソッドと execFile メソッドは、子プロセスの終了時にトリガーされる追加のコールバック関数も提供します。次に、詳細な分析を行います。 exec

exec メソッドは bash コマンドの実行に使用され、そのパラメータはコマンド文字列です。たとえば、現在のディレクトリ内のファイルの数をカウントするには、exec 関数は次のように記述されます。

const { exec } = require("child_process")
exec("find . -type f | wc -l", (err, stdout, stderr) => {
  if (err) return console.error(`exec error: ${err}`)
  console.log(`Number of files ${stdout}`)
})

exec は、新しいサブプロセスを作成し、その実行結果をキャッシュし、実行後にコールバック関数を呼び出します。完成されました。

exec コマンドは比較的危険だと思われたかもしれませんが、ユーザーが指定した文字列を exec 関数のパラメーターとして使用すると、次のようなコマンド ライン インジェクションのリスクに直面することになります。 #

find . -type f | wc -l; rm -rf /;

さらに、exec はすべての出力結果をメモリにキャッシュするため、データが比較的大きい場合は、spawn の方が適切な選択肢になります。

execFile

execFile と exec の違いは、シェルを作成せず、コマンドを直接実行するため、より効率的です。例:

const { execFile } = require("child_process")
const child = execFile("node", ["--version"], (error, stdout, stderr) => {
  if (error) throw error
  console.log(stdout)
})

作成シェルがないため、プログラムのパラメータが配列として渡されるため、安全性が高いです。

spawn

spawn 関数は execFile に似ています。デフォルトではシェルは開かれませんが、異なる点は、execFile がコマンド ラインの出力をキャッシュし、その結果をコールバックに渡すことです。関数では、spawn はストリーム出力を使用しますが、ストリームを使用すると、入力と出力を接続するのに非常に便利です。たとえば、典型的な

wc

コマンド:

const child = spawn("wc")
process.stdin.pipe(child.stdin)
child.stdout.on("data", data => {
  console.log(`child stdout:\n${data}`)
})
このとき、入力はユーザーが復帰 ctrl D

をトリガーすると、コマンドの実行が開始され、結果が標準出力から出力されます。

wc は Word Count の略語で、単語数をカウントするために使用されます。構文は次のとおりです:

wc [OPTION]... [FILE]...
ターミナルで wc コマンドを入力して Enter キーを押すと、 、カウントはキーボードからのものです。ターミナルに文字を入力し、もう一度 Enter キーを押し、

Ctrl D

を押して統計結果を出力します。

現在のディレクトリ内のファイル数を数えるなど、パイプラインを介して複雑なコマンドを組み合わせることができます。Linux コマンド ラインでは、次のように記述されます:

find . -type f | wc -l
Node.js での記述方法 コマンド ラインとまったく同じです:

const find = spawn("find", [".", "-type", "f"])
const wc = spawn("wc", ["-l"])
find.stdout.pipe(wc.stdin)
wc.stdout.on("data", (data) => {
  console.log(`Number of files ${data}`)
})

spawn には豊富なカスタム構成があります。たとえば:

const child = spawn("find . -type f | wc -l", {
  stdio: "inherit", // 继承父进程的输入输出流
  shell: true, // 开启命令行模式
  cwd: "/Users/keliq/code", // 指定执行目录
  env: { ANSWER: 42 }, // 指定环境变量(默认是 process.env)
  detached: true, // 作为独立进程存在
})

fork

fork 関数は次のとおりです。 fork によって作成された子を使用する spawn 関数のバリアント。プロセスと親プロセスの間に通信チャネルが自動的に作成され、send メソッドが子プロセスのグローバル オブジェクト プロセスにマウントされます。たとえば、親プロセスのparent.jsコード:

const { fork } = require("child_process")
const forked = fork("./child.js")

forked.on("message", msg => {
  console.log("Message from child", msg);
})

forked.send({ hello: "world" })

子プロセスのchild.jsコード:

process.on("message", msg => {
  console.log("Message from parent:", msg)
})

let counter = 0
setInterval(() => {
  process.send({ counter: counter++ })
}, 1000)

fork("child.js")

を呼び出すとき、実際には、node を使用してファイル内のコードを実行するだけです。これは、

spawn('node', ['./child.js']) と同等です。 fork の一般的なアプリケーション シナリオは次のとおりです。 Node.js を使用して http サービスを作成する場合、ルートが compute

の場合、時間のかかる操作が実行されます。

const http = require("http")
const server = http.createServer()
server.on("request", (req, res) => {
  if (req.url === "/compute") {
    const sum = longComputation()
    return res.end(Sum is ${sum})
  } else {
    res.end("OK")
  }
})

server.listen(3000);
次のコードを使用して、この時間のかかる操作をシミュレートできます:
const longComputation = () => {
  let sum = 0;
  for (let i = 0; i < 1e9; i++) {
    sum += i
  }
  return sum
}

那么在上线后,只要服务端收到了 compute 请求,由于 Node.js 是单线程的,耗时运算占用了 CPU,用户的其他请求都会阻塞在这里,表现出来的现象就是服务器无响应。

解决这个问题最简单的方法就是把耗时运算放到子进程中去处理,例如创建一个 compute.js 的文件,代码如下:

const longComputation = () => {
  let sum = 0;
  for (let i = 0; i < 1e9; i++) {
    sum += i;
  }
  return sum
}

process.on("message", msg => {
  const sum = longComputation()
  process.send(sum)
})

再把服务端的代码稍作改造:

const http = require("http")
const { fork } = require("child_process")
const server = http.createServer()
server.on("request", (req, res) => {
  if (req.url === "/compute") {
    const compute = fork("compute.js")
    compute.send("start")
    compute.on("message", sum => {
      res.end(Sum is ${sum})
    })
  } else {
    res.end("OK")
  }
})
server.listen(3000)

这样的话,主线程就不会阻塞,而是继续处理其他的请求,当耗时运算的结果返回后,再做出响应。其实更简单的处理方式是利用 cluster 模块,限于篇幅原因,后面再展开讲。

总结

掌握了上面四种创建子进程的方法之后,总结了以下三条规律:

  • 创建 node 子进程用 fork,因为自带通道方便通信。
  • 创建非 node 子进程用 execFile 或 spawn。如果输出内容较少用 execFile,会缓存结果并传给回调方便处理;如果输出内容多用 spawn,使用流的方式不会占用大量内存。
  • 执行复杂的、固定的终端命令用 exec,写起来更方便。但一定要记住 exec 会创建 shell,效率不如 execFile 和 spawn,且存在命令行注入的风险。

更多编程相关知识,请访问:编程视频!!

以上がNode.js で子プロセスを作成する方法の詳細な分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はjuejin.cnで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。