「学习笔记」child_process

child_process child_process 用于创建衍生子进程。Node 和衍生的子进程建立 stdin(标准输入), stdout(标准输出), stderr(标准错误) 管道。child_process.spawn, child_process.fork, child_process.exec, child_process.execFile 都会返回 ChildProcess 实例。ChildProcess 实例实现了 EventEmitter API,可以在子进程实例上添加事件的回调函数。进程之间可以通过事件消息系统进行互相通信。
child_process.spawn
启动一个子进程,执行命令。spawn 的接口定义: spawn(command: string, args: ReadonlyArray, options: SpawnOptions): ChildProcess

  • command, 需要运行的命令
  • args, 运行命令的参数, 是一个字符串数组
  • options, 配置项
    • options.cwd 子进程的工作目录
    • options.env 环境变量, 使用 key, value 配置环境变量
    • 等等
  • 返回值 ChildProcess, 返回 ChildProcess 的实例
// 例子 const { spawn, spawnSync } = require('child_process') const path = require('path')const cp = spawn('ls', ['-a'], { cwd: path.resolve(__dirname, '../Movies') })cp.stdout.on('data', (data) => { console.log(`子进程输出:' ${data}`) })cp.on('exit', (code, signal) => { console.log('子进程退出:', `code ${code} and signal ${signal}`) })

输出结果:
「学习笔记」child_process
文章图片

默认情况下,子进程的标准输入、标准输出和标准错误被重定向到 ChildProcess 对象上相应的 subprocess.stdin, subprocess.stdout 和 subprocess.stderr 流。可以设置 options.stdio: inherit, 传入父进程,
const { spawn, spawnSync } = require('child_process') const path = require('path')const cp = spawn('ls', ['-a'], { cwd: path.resolve(__dirname, '../Movies'), stdio: 'inherit' })

child_process.spawnSync
child_process.spawn 的同步版本。child_process.spawnSync 返回 Object。Object 包含了: pid(子进程pid), stdout(标准输出), stderr(标准错误) 等等。不同之处在于该函数在子进程完全关闭之前不会返回。
const { spawnSync } = require('child_process') const path = require('path')const obj = spawnSync('ls', ['-a'],{ cwd: path.resolve(__dirname, '../Movies') })console.log('pid', `${obj.pid}`) console.log('stdout', `${obj.stdout}`)

child_process.exec
创建一个衍生的 shell, 然后可以在衍生的 shell 之中执行命令。exec 实现了函数重载。第二个参数可以是配置项,或者是 callback。如果第二个参数是配置项,那么第三个参数就是 callback。
const { exec } = require('child_process') const path = require('path')exec('ls', (error, stdout, stderr) => { if (error) { console.error('error:', error); return; } console.log('stdout: ' + stdout); console.log('stderr: ' + stderr); })// 第二个参数可以是配置项 exec('ls -a', { cwd: path.resolve(__dirname, '../Movies'), }, (error, stdout, stderr) => { if (error) { console.error('error:', error); return; } console.log('stdout: ' + stdout); console.log('stderr: ' + stderr); })

callback 的三个参数分别是错误实例(如果执行成功, error 等于 null), stdout 标准输出, stderr 标准错误。
child_process.execSync
child_process.exec 的同步版本。child_process.execSync 方法返回标准输出,不同之处在于该方法在子进程完全关闭之前不会返回。
const { execSync } = require('child_process') const path = require('path')const stdout = execSync('ls -a', { cwd: path.resolve(__dirname, '../Movies') })console.log('stdout:', `${stdout}`)

child_process.exec 和 child_process.spawn 区别
  • spawn
    1. 不会创建衍生的 shell
    2. 流式的传输子进程产生的数据
    3. 没有数据大小的限制
  • exec
    1. 会创建衍生的 shell
    2. 最大传输 200kb 的数据
    3. 会缓存数据, 进程关闭后传输数据
spawn 适合巨大长时间的传输数据。exec 适合需要多次,小量的情况。
child_process.fork
child_process.fork, 用于在子进程中运行模块。child_process.fork(modulePath [, args] [, options])
  • modulePath, 需要在子进程中运行的模块地址
  • args, 字符串参数列表
  • options 配置项
    • execPath, 用来创建子进程的可执行文件。我们可以通过配置这个参数,指定不同版本的 node 创建子进程。
    • execArgv, 传给可执行文件的字符串参数列表。
    • silent, 子进程的标准输出, 是否从父进程进行继承。默认是 false 进行继承。如果设置为 true 则直接 pipe 向子进程的 child.stdin, child.stdout 等。
    • stdio, 用于配置在父进程和子进程之间建立的管道
// 子进程的代码 console.log('我是子进程')

const { fork } = require('child_process') const { resolve } = require('path')// 我是子进程 fork(resolve(__dirname, './cp.js'), { silent: false })// 没有打印 fork(resolve(__dirname, './cp.js'), { silent: true })

const { fork } = require('child_process') const { resolve } = require('path')const cp = fork(resolve(__dirname, './cp.js'), { silent: true })cp.stdout.on('data', function (data) { // stdout 中输出: 我是子进程 console.log('stdout 中输出:', `${data}`) })

通过 stdout 属性,可以获取到子进程输出的内容
child_process.execFile
child_process.execFile 不会创建衍生的 shell。效率要比 exec 要高。child_process.execFile(file[, args] [, options] [, callback])
  • file, 可以是执行文件的名字,或者路径。
const { execFile } = require('child_process') const { resolve } = require('path')execFile('node', [resolve(__dirname, './cp.js')], (err, stdout, stderr) => { if (err) { throw err } console.log(`stdout: ${stdout}`) })

child_process.exec 和 child_process.execFile 的区别
exec 内部通过调用 execFile 来实现。而 execFile 内部通过调用 spawn 来实现。
事件 ChildProcess 实例上可以监听很多事件
  • close, 子进程的 stdio 流关闭时触发
  • disconnect, 父进程手动调用 child.disconnect 函数时触发
  • error, 产生错误时会触发
  • exit, 子进程退出时触发
  • message, 子进程使用 process.send 函数来传递消息时触发
const { fork } = require('child_process'); const cp = fork('./cp.js')cp.on('close', (code, signal) => { console.log('close 事件:', code, signal); })cp.on('disconnect', () => { console.log('disconnect 事件...'); })cp.on('error', (code, signal) => { console.log('error 事件:', code, signal); })cp.on('exit', (code, signal) => { console.log('exit 事件:', code, signal); })cp.on('message', (val) => { console.log('message 事件:', val); })

进程之间的通信 创建子进程后, 父进程与子进程之间将会创建 IPC 通道,父子进程之间通过 message 和 send 来通信。
// 父进程 const { fork } = require('child_process'); const cp = fork('./cp.js')cp.on('message', (msg) => { console.log('message: ' + JSON.stringify(msg)) })

// 子进程 process.send({ msg: '我是子进程' })

【「学习笔记」child_process】父进程也可以向子进程发送消息使用 cp.send
// 父进程 const { fork } = require('child_process'); const cp = fork('./cp.js')cp.send({ msg: '我是父进程' })

参考
  • Node.js v16.8.0 文档
  • Node.js Child Processes: Everything you need to know
  • exec vs execFile nodeJs
  • Node.js Spawn vs. Execute
  • Nodejs进阶:如何玩转子进程(child_process)
  • 深入理解Node.js 中的进程与线程
  • Node.js process 模块学习指南
  • 玩转 node 子进程 — child_process

    推荐阅读