• 技术文章 >web前端 >js教程

    深入浅析Node.js中创建子进程的方法

    青灯夜游青灯夜游2021-10-12 10:05:48转载105
    本篇文章带大家了解一下Node.js中的子进程,介绍一下Node.js中创建子进程的四种方法,希望对大家有所帮助!

    众所周知,Node.js 是单线程、异步非阻塞的程序语言,那如何充分利用多核 CPU 的优势呢?这就需要用到 child_process 模块来创建子进程了,在 Node.js 中,有四种方法可以创建子进程:

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

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

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

    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 的时候,执行一个耗时的运算。

    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.js中创建子进程的方法的详细内容,更多请关注php中文网其它相关文章!

    声明:本文转载于:掘金社区,如有侵犯,请联系admin@php.cn删除
    专题推荐:Node.js 子进程
    上一篇:Angular如何进行优化?性能优化方案浅析 下一篇:手把手带你了解JavaScript运算符基础
    大前端线上培训班

    相关文章推荐

    • Nodejs进阶学习:深入了解异步I/O和事件循环• 浅谈NodeJS获取程序退出码的方法• Node项目如何配置环境,让其支持可扩展?• 图文结合带你搞懂Nodejs中的事件循环• 怎么在Nodejs或者浏览器直接运行esm代码• 分场景讲解两个 Node.js 进程间如何进行通信!

    全部评论我要评论

  • 取消发布评论发送
  • 1/1

    PHP中文网