简介nodejs的主事件循环是单线程的。Nodejs本身也维护了一个WorkerPool来处理一些耗时的操作。我们也可以使用nodejs线程提供的worker_threads手动创建新的来执行自己的任务。本文将介绍一种新的nodejs任务执行方式,子进程。childprocesslib/child_process.js提供了child_process模块??,我们可以通过它来创建子进程。请注意,worker_threads创建子线程,而child_process创建子进程。在child_process模块中,可以同步或异步创建进程。同步创建方法只是在异步创建方法后面加上Sync。创建的进程由ChildProcess类表示。我们看下ChildProcess的定义:interfaceChildProcessextendsevents.EventEmitter{stdin:Writable|无效的;标准输出:可读|无效的;标准错误:可读|无效的;只读通道?:管道|无效的;只读标准输入输出:[可写|null,//标准输入可读|null,//标准输出可读|null,//stderr可读|可写|空|undefined,//额外的可读性|可写|空|未定义//额外的];只读被杀死:布尔值;只读pid:数字;只读连接:布尔值;只读退出代码:数字|无效的;只读信号代码:NodeJS.Signals|无效的;只读生成参数:字符串[];只读生成文件:字符串;kill(signal?:NodeJS.Signals|number):布尔值;发送(消息:可序列化,回调?:(错误:错误|空)=>无效):布尔值;发送(消息:Serializable,sendHandle?:SendHandle,回调?:(错误:错误|null)=>void):布尔值;发送(消息:可序列化,sendHandle?:SendHandle,选项?:MessageOptions,回调?:(错误:错误|null)=>void):布尔值;断开连接():无效;未引用():无效;参考():无效;/***events.EventEmitter*1.close*2.disconnect*3.error*4.exit*5.message*/...}可以看到ChildProcess也是一个EventEmitter,所以它可以发送和接收eventChildProcess可以接收5种事件,分别是close、disconnect、error、exit和message。当调用父进程中的subprocess.disconnect()或子进程中的process.disconnect()时会触发disconnect事件。当无法创建进程,无法杀死进程,给子进程的消息失败时,会触发error事件。exit事件在子进程结束时触发。当关闭子进程的stdio流时会触发close事件。注意close事件不同于exit事件,因为多个进程可能共享同一个stdio,所以发送exit事件并不一定会触发close事件。查看关闭和退出的示例:const{spawn}=require('child_process');constls=spawn('ls',['-lh','/usr']);ls.stdout.on('data',(data)=>{console.log(`stdout:${data}`);});ls.on('close',(code)=>{console.log(`subprocessusecode${code}Closeallstdio`);});ls.on('exit',(code)=>{console.log(`子进程使用代码${code}退出`);});最后是消息事件,当子进程使用process.send()发送消息时触发。ChildProcess中有几个标准的流属性,分别是stderr、stdout、stdin和stdio。stderr、stdout、stdin很好理解,分别是标准错误、标准输出和标准输入。让我们看看标准输出的使用:const{spawn}=require('child_process');constsubprocess=spawn('ls');subprocess.stdout.on('data',(data)=>{console.log(`接收数据块${data}`);});stdio实际上是stderr,stdout,stdin的集合:readonlystdio:[Writable|null,//标准输入可读|null,//标准输出可读|null,//stderr可读|可写|空|undefined,//额外的可读性|可写|空|未定义//额外的];其中stdio[0]表示标准输入,stdio[1]表示标准输出,stdio[2]表示标准错误。如果在通过stdio创建子进程时将三个标准流设置为pipe以外的任何内容,则stdin、stdout和stderr将为空。让我们看一个使用stdio的例子:constassert=require('assert');constfs=require('fs');constchild_process=require('child_process');constsubprocess=child_process.spawn('ls',{stdio:[0,//将父进程的标准输入用于子进程。'pipe',//将子进程的标准输出通过管道传递给父进程。fs.openSync('err.out','w')//将子进程的stderr指向一个文件。]});assert.strictEqual(subprocess.stdio[0],null);assert.strictEqual(subprocess.stdio[0],subprocess.stdin);assert(subprocess.stdout);assert.strictEqual(subprocess.stdio[1],subprocess.stdout);assert.strictEqual(subprocess.stdio[2],null);assert.strictEqual(subprocess.stdio[2],subprocess.stderr);通常,父进程维护一个子进程的引用计数,子进程退出后父进程才会退出。这个引用就是ref,如果调用了unref方法,就允许父进程独立于子进程退出。const{spawn}=require('child_process');constsubprocess=spawn(process.argv[0],['child_program.js'],{detached:true,stdio:'ignore'});subprocess.unref();最后看看如何通过ChildProcess发送消息:subprocess.send(message[,sendHandle[,options]][,callback])其中message是要发送的消息,callback是发送消息后的回调。sendHandle比较特殊,它可以是TCP服务器或套接字对象,通过将这些句柄传递给子进程。子进程会将句柄传递给message事件中的Callback函数,这样就可以在子进程中进行处理了。下面看一个传递TCP服务器的例子,先看主流程:constsubprocess=require('child_process').fork('subprocess.js');//打开服务器对象,发送句柄。constserver=require('net').createServer();server.on('connection',(socket)=>{socket.end('handledbyparentprocess');});server.listen(1337,()=>{subprocess.send('server',server);});再看子进程:process.on('message',(m,server)=>{if(m==='server'){server.on('connection',(socket)=>{socket.end('由子进程处理');});}});可以看到子进程已经收到服务器句柄,并在子进程中监听连接事件。让我们看一个传递套接字对象的例子:onst{fork}=require('child_process');constnormal=fork('subprocess.js',['normal']);constspecial=fork('subprocess.js',['special']);//启动服务器并将套接字发送给子进程。//使用`pauseOnConnect`防止套接字在发送到子进程之前被读取。constserver=require('net').createServer({pauseOnConnect:true});server.on('connection',(socket)=>{//特殊优先级。if(socket.remoteAddress==='74.125.127.100'){special.send('socket',socket);return;}//正常优先级。normal.send('socket',socket);});server.listen(1337);subprocess.js的内容:process.on('message',(m,socket)=>{if(m==='socket'){if(socket){//检查客户端套接字是否存在。//socket可以在子进程发送和接收之间关闭socket.end(`请求使用${process.argv[2]}优先处理`);}}});主进程创建两个子进程,一个用于特殊优先级,一个用于普通优先级。异步创建进程child_process模块??有4种异步创建进程的方式,分别是child_process.spawn()、child_process.fork()、child_process.exec()和child_process.execFile()。先看各个方法的定义:child_process.spawn(command[,args][,options])child_process.fork(modulePath[,args][,options])child_process.exec(command[,options][,callback])child_process.execFile(file[,args][,options][,callback])其中child_process.spawn是基础,会异步生成一个新进程,其他fork、exec、execFile都是基于spawn生成的。fork产生一个新的Node.js进程。exec和execFile在带有回调的新进程中执行新命令。它们的区别在于,在Windows环境下,如果要执行.bat或.cmd文件,没有shell终端是无法执行的。这个时候只能用exec启动。无法执行execFile。或者你可以使用spawn。让我们看一个仅在Windows上使用spawn和execinwindows://的示例。const{spawn}=require('child_process');constbat=spawn('cmd.exe',['/c','my.bat']);bat.stdout.on('data',(data)=>{console.log(data.toString());});bat.stderr.on('data',(data)=>{console.error(data.toString());});bat.on('exit',(code)=>{console.log(`childprocessexit,exitcode${code}`);});const{exec,spawn}=require('child_process');exec('my.bat',(err,stdout,stderr)=>{if(err){console.error(err);return;}console.log(stdout);});//文件名中有空格的脚本:constbat=spawn('"myscript.cmd"',['a','b'],{shell:true});//or:exec('"myscript.cmd"ab',(err,stdout,stderr)=>{//...});同步创建进程您可以使用child_process.spawnSync()、child_process.execSync()和child_process.execFileSync()来同步创建进程。同步方法将阻塞Node.js事件循环,暂停任何其他代码的执行,直到子进程退出。通常对于一些脚本任务,比较常见的是使用同步创建过程。本文作者:flydean程序那些事儿本文链接:http://www.flydean.com/nodejs-childprocess/本文来源:flydean的博客欢迎关注我的公众号:《程序那些事儿》最通俗的解读,最深刻的干货,最简洁的教程,还有很多你不知道的小技巧等你来发现!
