关于node.js:Node多进程exec方法执行流程源码分析

34次阅读

共计 3511 个字符,预计需要花费 9 分钟才能阅读完成。

在自研脚手架阶段,对 node 的同步 / 异步执行进行了应用;然而未能深层次去理解为何这么设计,这次也是通过问题的形式来进行了一些思考。
⚠️注:目前是 12 版本的 node;你可能当初用的版本是 16 版以上用 TS 来写的;然而 node 的基本上的思路是没有太多变动的。

思考

问题一:exec 和 execFile 到底什么区别?问题二:为什么 exec/execFile/fork 都是通用 spawn 实现的,spawn 的作用到底是什么?问题三:为什么 spawn 没有回调;exec 和 execFile 可能回调?问题四:为什么 spawn 调用后,需手动调用 child(spawn 返回值).stdout.on('data',callback);spawn.stdout 和 spawn.stderr 到底是什么?问题五:为什么有 data/errer/exit 这么多种回调;他们的执行程序到底是什么?

一、源码剖析

源码分析方法:依据办法的执行调用程序来进行剖析源码

exec 源码剖析

源码目录构造

child_process.js

  • exec
  • execFile
  • spawn

internal/child_process.js

  • ChildProcess
  • spawn
代码执行流程
  1. 执行本地代码

    首先在本地创立 index.js;

    const cp = require('child-process');
    const child = cp.exec('ls -la|grep node_modules', function(err, stdout stderr){console.log(err, stdout, stderr);
    });

  1. 执行 cp.exec 办法

    将会调用 node 的内置库【child_process】中的【exec】办法,进行参数的标准化解决【normalizeExecArgs】

function exec (command, options, callback) {const opts = noramlizeExeArgs(command, options, callback);
    return module.exports.execFile(opts.file,
                                   opts.optionns,
                                   opts.callback);
}
入参解决:
opts:{
file : "ls -al|grep node_modules",
options : {shell : ture},
callback : .....,
}
入参解决后的后果:
  • file 是输出的命令
  • options 增加了 shell 为 true
  • callback 没有变动
返回后果:
  • return module.exports.execFile
  • 间接调用 execFile 办法

通过 exec 办法中的 noramilizeExecArgs 办法将参数转化成 execFile 办法的参数一样;

  1. 进入 execFile 办法

    a. 首先还是会进行一个参数的标准化解决【normalizeExecFileArgs】
    b. 调用 spawn child 办法,次要目标是创立一个子过程并且对它进行异步执行

const child = spawn (file, args, {
    cwd: options.cwd,
    env: options.env,
    gid: options.gid,
    uid: options.uid,
    shell: options.shell,
    .....
});
spawn 参数阐明:
  • file: “ls -al|grep node_modules”,
  • agrs: 没有参数,
  • object:{shell: true}; 中只有 shell 参数显示 true,须要用外部的 shell 脚本去执行.
  1. 调用 spawn 办法

    a. 进行参数的标准化解决【normalizeExecFileArgs】
    b. 调用 child = new ChildProcess()

function spawn(file, args, options) {const opts = normalizeSpawnArguments(file, args, options);
    const child = new ChildProcess();
// 在 child,创立的子过程当中有一个_handle 是理论的过程,_handle= Process{onexit:Function,...}; 调用的形式是须要用 spwan 来调用。spwan 最终执行的是_handle 的 spwan;};
opts 返回的参数解决:
file: '/bin/sh'

// 这个是 shell 的主命令
在本地执行下:

shell 的应用
办法一:间接执行 shell 文件

    /bin/sh test.shell

办法二:间接执行 shell 语句

    /bin/sh -c "ls-al|grep node_modules"

因为传入了参数: shell = true;所以 file 就设置为/bin/sh;示意用 shell 主命令来执行。

args: ["bin/sh", "-c", "ls -al|grep node_modules"]
options: {
cwd: null,
...
shell: true,
...
}
envPairs: // 操作系统的环境变量数据

  1. new ChildProcess

    a. 起源是 node 的外部库【interinternal/child_process】,node 的内置库才能够调用到。
    b. 这个外部库中有一个 ChildProcess 类;对应的就是子过程类,(就是子过程);所以在 spawn 中 new ChildProcess() 就创立了一个子过程类;

child_process.js

const child_process = require('internal/child_process')
 
const {
    ...
    ChildProcess,
    ...
    } = child_process; 
  1. interinternal/child_process 库中的 ChildProcess

internal/child_process.js

const {Process} =  internalBinding('process_warp');
// 引入 c ++ 文件
 this._handle = Process;

  1. 创立子过程之后,调用 spawn 办法,child.spwan; 利用子过程去执行命令,执行完之后,间接返回,return child

    function spawn(file, args, options) {const opts = normalizeSpawnArguments(file, args, options);
     const child = new ChildProcess();
    ...
    child.spawn({
     file: opts.file, // "/bin/sh"
     args: opts.args,  // ["/bin/sh", "-c", "ls -a | grep node_modules"]
     cwd: options.cwd,
     ...
    })
    
     return child;

    child.spawn 是把命令执行起来的办法,位于 internal/child_process 文件中,最外围的是 this._handle.spawn 办法,之前只是创立了过程对象,没有调配任何理论资源。调用 this._handle.spawn 过程就被执行起来。相应也会生成子过程 ID;

  2. spawn 执行完之后,回到 execFile 中,进行往下执行

    a. 对输入流进行监听: child.stdout.on('data')
    b. 对谬误流进行监听: child.stderr.on('data')

....
if (child.stdout) {if(encoding) {child.stdout.setEncoding(encoding);
child,stdout.on('data', function onChildStdout(chunk){
        const encoding = child.stdout.readableEncoding;
            ...
        }
     }
}
...
if (child.stderr) {if(encoding) {child.stderr.setEncoding(encoding);
child,stdout.on('data', function onChildStderr(chunk){
        const encoding = child.stderr.readableEncoding;
            ...
        }
     }
}
...

child.addListener('close', exithandler);
child.addListener('error', errorhander);
return chuild;

这也是为什么 execFile 能够返回 callback 的起因;手动做了监听;

c. 对过程进行 exiterror的监听;

总结

以上是 exec 办法执行流程的剖析,如图:

正文完
 0