>  기사  >  웹 프론트엔드  >  Node.js에서 하위 프로세스를 생성하는 방법에 대한 심층 분석

Node.js에서 하위 프로세스를 생성하는 방법에 대한 심층 분석

青灯夜游
青灯夜游앞으로
2021-10-12 10:05:482904검색

이 글은 Node.js의 하위 프로세스를 안내하고 Node.js에서 하위 프로세스를 만드는 네 가지 방법을 소개합니다. 모든 사람에게 도움이 되기를 바랍니다.

Node.js에서 하위 프로세스를 생성하는 방법에 대한 심층 분석

우리 모두 알고 있듯이 Node.js는 단일 스레드, 비동기, 비차단 프로그래밍 언어인데, 멀티 코어 CPU의 장점을 최대한 활용하는 방법은 무엇일까요? 이를 위해서는 하위 프로세스를 생성하려면 child_process 모듈이 필요합니다. Node.js에는 하위 프로세스를 생성하는 네 가지 방법이 있습니다:

  • execexec

  • execFile

  • spawn

  • fork

【推荐学习:《nodejs 教程》】

上面四个方法都会返回 ChildProcess 实例(继承自 EventEmitter),该实例拥有三个标准的  stdio 流:

  • child.stdin

  • child.stdout

  • child.stderr

子进程生命周期内可以注册监听的事件有:

exit:子进程结束时触发,参数为 code 错误码和 signal 中断信号。

close:子进程结束并且 stdio 流被关闭时触发,参数同 exit 事件。

disconnect:父进程调用 child.disconnect() 或子进程调用 process.disconnect() 时触发。

error:子进程无法创建、或无法被杀掉、或发消息给子进程失败时触发。

message:子进程通过 process.send() 发送消息时触发。

spawn:子进程创建成功时触发(Node.js v15.1版本才添加此事件)。

execexecFile 方法还额外提供了一个回调函数,会在子进程终止的时候触发。接下来进行详细分析:

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 的区别在于它并不会创建 shell,而是直接执行命令,所以会更高效一点,例如:

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

由于没有创建 shell,程序的参数作为数组传入,因此具有较高的安全性。

spawn

spawn 函数和  execFile 类似,默认不开启 shell,但区别在于 execFile 会缓存命令行的输出,然后把结果传入回调函数中,而 spawn 则是以流的方式输出,有了流,就能非常方便的对接输入和输出了,例如典型的 wc 命令:

const child = spawn("wc")
process.stdin.pipe(child.stdin)
child.stdout.on("data", data => {
  console.log(`child stdout:\n${data}`)
})

此时就会从命令行 stdin 获取输入,当用户触发回车 + ctrl D 时就开始执行命令,并把结果从 stdout 输出。

wc 是 Word Count 的缩写,用于统计单词数,语法为:

wc [OPTION]... [FILE]...

如果在终端上输入 wc 命令并回车,这时候统计的是从键盘输入终端中的字符,再次按回车键,然后按 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 函数是 spawn 函数的变体,使用 fork 创建的子进程和父进程之间会自动创建一个通信通道,子进程的全局对象 process 上面会挂载 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

execFile

🎜🎜spawn🎜🎜🎜fork🎜🎜【추천 학습 : " nodejs tutorial🎜》】🎜🎜위의 네 가지 방법은 ChildProcess 인스턴스(EventEmitter에서 상속됨): 세 개의 표준 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에만 추가되었습니다). 🎜🎜 execexecFile 메서드는 하위 프로세스가 종료될 때 트리거되는 추가 콜백 함수도 제공합니다. 다음으로 자세한 분석: 🎜

exec

🎜exec 메소드는 bash 명령을 실행하는 데 사용되며 해당 매개변수는 명령 문자열입니다. 예를 들어 현재 디렉터리의 파일 수를 계산하기 위해 exec 함수는 다음과 같이 작성됩니다. 🎜
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);
🎜exec는 새 하위 프로세스를 생성한 다음 실행 결과를 캐시하고 작업이 완료된 후 콜백 함수를 호출합니다. 🎜🎜exec 명령이 상대적으로 위험하다고 생각하셨을 수도 있습니다. 사용자가 제공한 문자열을 exec 함수의 매개변수로 사용하면 다음과 같은 명령줄 삽입 위험에 직면하게 됩니다. 🎜
const longComputation = () => {
  let sum = 0;
  for (let i = 0; i < 1e9; i++) {
    sum += i
  }
  return sum
}
🎜또한, exec는 메모리에 캐시되기 때문에 모든 출력 결과에서 데이터가 상대적으로 클 경우 generate가 더 나은 선택이 됩니다. 🎜

execFile

🎜execFile과 exec의 차이점은 셸을 생성하지 않고 명령을 직접 실행하므로 더 효율적이라는 것입니다. 예: 🎜
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)
})
🎜쉘이 생성되지 않고 프로그램의 매개변수가 배열로 전달되어 보안성이 높습니다. 🎜

spawn

🎜spawn 함수는 execFile과 유사합니다. 셸은 기본적으로 활성화되어 있지 않지만 차이점은 execFile이 명령줄의 출력을 캐시한 다음 결과를 콜백 함수에 전달합니다. 스트림을 사용하면 입력과 출력을 연결하는 것이 매우 편리합니다. 예를 들어 일반적인 wc 명령을 얻을 수 있습니다. 이때 명령줄 stdin에서 사용자가 Enter + ctrl D를 트리거하면 명령이 실행되고 결과가 stdout에서 출력됩니다. 🎜
🎜wc는 Word Count의 약어입니다. 단어 수를 세는 데 사용됩니다. 구문은 다음과 같습니다. 🎜
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)
🎜 터미널에 wc 명령을 입력하고 Enter 키를 누르면 터미널에 입력된 문자가 나타납니다. 키보드가 계산됩니다. 다시 Enter 키를 누른 다음 Ctrl + D를 눌러 통계 결과를 출력합니다. 🎜
🎜현재 디렉터리에 있는 파일 수를 세는 등 복잡한 명령도 파이프를 통해 결합할 수 있습니다. Linux 명령줄에서는 다음과 같이 작성됩니다. 🎜rrreee🎜Node.js의 작성 방법 🎜rrreee 🎜spawn에는 다음과 같은 풍부한 사용자 정의 구성이 있습니다. 🎜rrreee

fork

🎜fork 기능은 generate의 변형입니다. 함수는 포크를 사용하여 하위 프로세스와 상위 프로세스를 생성하고 통신 채널이 자동으로 생성되며 하위 프로세스의 전역 개체 프로세스에 전송 메서드가 탑재됩니다. 예를 들어 상위 프로세스 parent.js 코드: 🎜rrreee🎜자식 프로세스 child.js 코드: 🎜rrreee🎜 fork("child.js")를 호출할 때 노드는 실제로 다음을 실행하는 데 사용됩니다. 파일의 코드는 spawn('node', ['./child.js'])와 동일합니다. 🎜🎜fork의 일반적인 애플리케이션 시나리오는 다음과 같습니다. Node.js를 사용하여 http 서비스를 생성하는 경우 경로가 compute일 때 시간이 많이 걸리는 작업이 수행됩니다. 🎜rrreee🎜다음 코드를 사용하여 시간이 많이 걸리는 작업을 시뮬레이션할 수 있습니다.🎜
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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 juejin.cn에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제