当前位置: 首页 > 后端技术 > Node.js

Node多进程exec方法执行流程源码分析

时间:2023-04-03 23:14:34 Node.js

自研脚手架阶段,使用了node的同步/异步执行;但是一直没能理解为什么要这样设计,这次也是通过提问的方式进行一些思考。??注意:目前是node的12版本;您可能正在使用以TS编写的版本16或更高版本;但是node的基本思想没有太大变化。思考题1:exec和execFile有什么区别?问题2:为什么exec/execFile/fork是一般spawn实现的,spawn的作用是什么?问题三:spawn为什么没有回调;exec和execFile可以回调吗?问题4:为什么调用spawn后需要手动调用child(spawn返回值).stdout.on('data',callback);spawn.stdout和spawn.stderr到底是什么?问题5:为什么data/errer/exit回调那么多;他们的执行顺序是什么?1.源码分析源码分析方法:分析源码exec源码分析源码目录结构根据方法执行调用顺序child_process.jsexecexecFilespawnminternal/child_process.jsChildProcessspawn代码执行流程执行本地代码首先在本地创建index.js;constcp=require('子进程');constchild=cp.exec('ls-la|grepnode_modules',function(err,stdoutstderr){console.log(err,stdout,stderr);});执行cp.exec方法会调用node内置库[child_process]中的[exec]方法对参数进行标准化[normalizeExecArgs]返回模块。exports.execFile(opts.file,opts.optionns,opts.callback);}输入参数处理:opts:{file:"ls-al|grepnode_modules",options:{shell:ture},callback:.....,}输入参数处理后结果:file为输入命令选项添加shell为truecallback无变化返回结果:returnmodule.exports.execFile直接调用execFile方法通过exec方法中的noramilizeExecArgs方法,将参数转换为与execFile方法相同的参数;进入execFile方法a。首先,将一个参数标准化[normalizeExecFileArgs]b.调用spawnchild方法,主要目的是创建子进程并异步执行.uid,shell:options.shell,.....});spawn参数说明:file:"ls-al|grepnode_modules",agrs:noparameter,object:{shell:true};只有shell参数显示为true,需要执行内部shell脚本。调用spawn方法a。规范化参数【normalizeExecFileArgs】b.调用child=newChildProcess()functionspawn(file,args,options){constopts=normalizeSpawnArguments(file,args,options);constchild=newChildProcess();//在child中,创建的子进程之一有一个_handle是实际进程,_handle=Process{onexit:Function,...};调用方式是使用spwan调用spwan最终执行的是_handle的spwan;};opts返回的参数处理:file:'/bin/sh'//这是shell要在本地执行的主要命令:使用shell的方法一:直接执行shell文件/bin/shtest.shell方法2:直接执行shell语句/bin/sh-c"ls-al|grepnode_modules"因为传入参数:shell=true;所以文件设置为/bin/sh;意思是用shell主命令来执行。args:["bin/sh","-c","ls-al|grepnode_modules"]options:{cwd:null,...shell:true,...}envPairs://操作系统环境变量数据源newChildProcessa。是node的内部库[internal/child_process],只能调用node的内置库。b.这个内部库中有一个ChildProcess类;对应的是子进程类,(即子进程);所以在spawn中,newChildProcess()创建了一个子进程类;child_process.jsconstchild_process=require('internal/child_process')const{...ChildProcess,...}=child_process;internal/child_process库中的ChildProcessinternal/child_process.jsconst{Process}=internalBinding('process_warp');//导入c++文件this._handle=Process;创建子进程后,调用spawn方法,child.spwan;使用子进程执行命令,执行完直接返回,returnchild;functionspawn(file,args,options){constopts=normalizeSpawnArguments(file,args,options);constchild=newChildProcess();...child.spawn({file:opts.file,//"/bin/sh"args:opts.args,//["/bin/sh","-c","ls-a|grepnode_modules"]cwd:options.cwd,...})returnchild;child.spawn是执行命令的方法,位于internal/child_process文件中,核心就是这个。_handle.spawn方法之前只是创建了一个进程对象,并没有分配任何实际资源。调用this._handle.spawn进程被执行。相应的,也会生成子进程ID;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',functiononChildStdout(chunk){constencoding=child.stdout.readableEncoding;...}}}...if(child.stderr){if(encoding){child.stderr.setEncoding(encoding);child,stdout.on('data',functiononChildStderr(chunk){constencoding=child.stderr.readableEncoding;...}}}...child.addListener('close',exithandler);child.addListener('error',errorhander);returnchuld;这就是为什么execFile能返回callback的原因;手动监听;c.监听进程的退出和错误;总结1.exec方法的执行过程分析如图:2.exec/execFile/的区别fork/spawnexec:通过调用/bin/sh-c执行传入的shell脚本,底层是调用execFile;只处理参数execFile:原理是执行传入的文件和args,底层调用spawn方法创建并执行子进程,并手动创建一个回调函数,一次性返回所有stderr(错误流)和stdout(输出流)结果。适用于执行快速完成的任务,可以直接得到最终结果。spawn:原理是调用内部internal/child_process,强化ChilProcess的子进程对象。未创建子进程后,调用child.spawn方法创建子进程并执行命令。底层是调用internal/ChildProcessthis._handle.spawn执行子进程,执行C++的process_wrap文件。这个文件是为了帮助创建进程,因为内存空间是C++划分的,JS没有办法直接操作内存;process_wrap中提供的spawn方法是直接创建子进程并执行子调用,同步完成子进程的创建和调用;接下来执行异步流程,通过pipe管道进行单向数据通信。一共有三个流,输入流,输出流,错误流,每个流都会创建一个管道。通信结束后子进程发起onexit回调,同步Socket会执行close回调。fork:原理是调用spawn创建子进程并执行命令,使用node执行命令,通过setupchannel创建IPC通道,实现父子进程双向通信。