詳解node多進程child_process用法及原理

child_process用法

child_process的api:
  • 異步api:
    1. exec(cmd, options, callback)
    2. execFile(cmd, args, options, callback)
    3. fork (模塊路徑, args, options) // 不一樣的地方在于可以通信
    4. spawn(cmd, args, options)
    • exec用法:
      執(zhí)行shell腳本, 使用管道符也是可以的
      exec也是可以執(zhí)行文件的,只不過不能傳參數(shù)
      適合開銷比較小的任務
    const cp = require('child_process')
    cp.exec('ls -al|grep node_modules', { 
      timeout: 0, // 超時時間
      cwd: process.cwd(), // 可以改變當前的執(zhí)行路徑
      }, function (err, stdout, stderr) {
      // 執(zhí)行結(jié)果
    })
    
    • execFile用法:
      可以執(zhí)行文件,也可以執(zhí)行語句,可傳參
      適合開銷比較小的任務
      // 執(zhí)行文件,參數(shù)
    cp.execFile('ls', ['-al'], function (err, stdout, std,err) => {
      // 執(zhí)行結(jié)果
    })
    // 讓execFile執(zhí)行l(wèi)s -al|grep node_modules這種語句
    test.shell:
      ls -al|grep node_modules
      echo $1 // 打印參數(shù)
      echo $2
    index.js:
    cp.execFile(path.resolve(__dirname, 'test.shell'), ['-al', 'bl'], 
    function(err,   stdout, stderr) {
    })
    
    • fork用法
      用node執(zhí)行, 耗時操作且用node實現(xiàn),如下載文件
    // cp.fork(模塊路徑)
    // 和require一樣把文件執(zhí)行起來
    const child = cp.fork(path.resolve(__dirname, 'child_process'))
    console.log(process.pid)
    // 主進程向子進程通信
    child.send('hello child_process', () => {
      // child.disconnent() // 如果不斷開,兩邊會出現(xiàn)等待的情況
    })
    // 子進程向主進程通信
    child.on('message', msg => {
      
    })
    // child_process.js:
      console.log('aaa', process.pid)
      process.on('message', msg => {
      console.log(msg)
      // 很容易出現(xiàn)死循環(huán)
      })
      process.send('send msg to parent')
    // 進程不一樣,完全獨立,本質(zhì)也是調(diào)用spawn
    
    • spawn 用法
      spawn: 流式的,沒有回調(diào),適合耗時任務(比如:npm install), 需要不斷打印日志(不斷給用戶輸出日志)
cp.spawn(file, args, options) // 不支持回調(diào), exec,execFile底層都是spwan
const child = cp.spawn(path.resolve(__dirname, 'test.shell'), ['-al', '-bl'], {
  cwd: path.resolve('..'),
}) // 返回的是子進程
console.log(child.pid, process.pid)
// 監(jiān)聽成功
child.stdout.on('data', function(chunk) {
  console.log(chunk.toString())
})
// 監(jiān)聽失敗
child.stderr.on('data', function(chunk) {
  console.log(chunk.toString())
})

const code = `require('${rootFile}').call(null, ${JSON.stringify(args)})`
 // cp.spawn('cmd', ['/c', 'node', '-e', code]) // win下是這種結(jié)構(gòu)
 const child = spawn('node', ['-e', code], {
   cwd: process.cwd(), // 當前執(zhí)行未知的cwd
   stdio: 'inherit', // 默認是pipe,pipe必須通過on來接收信息,inherit不需要,實時反饋
 })
 child.on('error', e => {
   log.error(e.message)
   process.exit(1)
 })
child.on('exit', e => {
  log.verbose('命令執(zhí)行成功', e)
  process.exit(e)
})
  • 同步api:
    • execSync
    • execFileSync
    • spawnSync
const ret = cp.execSync('ls -al|grep node_modules') // 用的比較多,對腳本安全性沒有校驗
// 可以直接拿到結(jié)果
console.log(ret.toString())
const ret2 = cp.execFileSync('ls', ['-al'])
console.log(ret2.toString)
const ret3 = cp.spawnSync('ls', ['-al'])
console.log(ret3.stdout.toString()) // 返回的是一個對象

child_process原理

1. exec 和 execFile有什么區(qū)別?
  • 二者只有傳參不同
  • 底層調(diào)用的還是spawn
// exec源碼,處理一下參數(shù),調(diào)用的execFile
function exec(command, options, callback) {
  const opts = normalizeExecArgs(command, options, callback);
  return module.exports.execFile(opts.file,
                                 opts.options,
                                 opts.callback);
}
function execFile(file /* , args, options, callback */) {
  // ...
  // 底層調(diào)用的還是spawn
    const child = spawn(file, args, {
      cwd: options.cwd,
      env: options.env,
      gid: options.gid,
      shell: options.shell,
      signal: options.signal,
      uid: options.uid,
      windowsHide: !!options.windowsHide,
      windowsVerbatimArguments: !!options.windowsVerbatimArguments
    });
}
  1. exec: 原理是調(diào)用/bin/sh -c 執(zhí)行我們傳入的shell腳本,底層調(diào)用execFile
  2. execFile:原理是直接執(zhí)行我們傳入的file和args,底層調(diào)用spawn創(chuàng)建和執(zhí)行子進程,并建立回調(diào),一次性將所有的stdout和stderr結(jié)果返回
  3. spawn:原理是調(diào)用internal/child_process,實例化略ChildProcess子進程對象,再調(diào)用child.spawn創(chuàng)建 子進程并執(zhí)行命令,底層是調(diào)用了child.)handle.spawn執(zhí)行process_wrap中的spwan方法,執(zhí)行過程是異步的,執(zhí)行完畢后再通過PIPE進行單向數(shù)據(jù)通信,通信結(jié)束后子進程發(fā)起onexit回調(diào),同時Socket會執(zhí)行close回調(diào)。
  4. fork:原理是通過spawn創(chuàng)建子進程和執(zhí)行命令,采用node執(zhí)行命令,通過setupchannel創(chuàng)建IPC用于子進程和父進程之間的雙向通信。
2. data/error/exit/close回調(diào)的區(qū)別

data:用于主進程讀取數(shù)據(jù)過程中通過onStreamRead發(fā)起的回調(diào)
error: 命令執(zhí)行失敗后發(fā)起的回調(diào)
exit: 子進程關(guān)閉完成后發(fā)起的回調(diào)
close:子進程所有Socket通信端口全部關(guān)閉后發(fā)起的回調(diào)
stdout close/stderr close:特定的PIPE讀取完成后調(diào)用onReadableStreamEnd關(guān)閉Socket時發(fā)起的回調(diào)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容