nodejs中的子进程,深刻解析child_process模块和cluster模块


  node遵循的是单线程单进程的模式,node的单线程是指js的引擎只有一个实例,且在nodejs的主线程中执行,同时node以事件驱动的方式处理IO等异步操做。node的单线程模式,只维持一个主线程,大大减小了线程间切换的开销。javascript

  可是node的单线程使得在主线程不能进行CPU密集型操做,不然会阻塞主线程。对于CPU密集型操做,在node中经过child_process能够建立独立的子进程,父子进程经过IPC通讯,子进程能够是外部应用也能够是node子程序,子进程执行后能够将结果返回给父进程。java

  此外,node的单线程,以单一进程运行,所以没法利用多核CPU以及其余资源,为了调度多核CPU等资源,node还提供了cluster模块,利用多核CPU的资源,使得能够经过一串node子进程去处理负载任务,同时保证必定的负载均衡型。本文从node的单线程单进程的理解触发,介绍了child_process模块和cluster模块,本文的结构安排以下:node


  • node中的单线程和单进程
  • node中的child_process模块实现多进程
  • node中的cluster模块
  • 总结

原文的地址,在个人博客中:https://github.com/forthealll...git

若有帮助,您的star是对我最好的鼓励~github

1、node中的单线程和单进程

  首先要理解的概念是,node的单线程和单进程的模式。node的单线程于其余语言的多线程模式相比,减少了线程间切换的开销,以及在写node代码的时候不用考虑锁以及线程池的问题。node宣称的单线程模式,比其余语言更加适合IO密集型操做。那么一个经典的问题是:web

node是真的单线程的吗?ajax

提到node,咱们就能够马上想到单线程、异步IO、事件驱动等字眼。首先要明确的是node真的是单线程的吗,若是是单线程的,那么异步IO,以及定时事件(setTimeout、setInterval等)又是在哪里被执行的。shell

严格来讲,node并非单线程的。node中存在着多种线程,包括:安全

  • js引擎执行的线程
  • 定时器线程(setTimeout, setInterval)
  • 异步http线程(ajax)

....服务器

  咱们平时所说的单线程是指node中只有一个js引擎在主线程上运行。其余异步IO和事件驱动相关的线程经过libuv来实现内部的线程池和线程调度。libv中存在了一个Event Loop,经过Event Loop来切换实现相似于多线程的效果。简单的来说Event Loop就是维持一个执行栈和一个事件队列,当前执行栈中的若是发现异步IO以及定时器等函数,就会把这些异步回调函数放入到事件队列中。当前执行栈执行完成后,从事件队列中,按照必定的顺序执行事件队列中的异步回调函数。

default

上图中从执行栈,到事件队列,最后事件队列中按照必定的顺序执行回调函数,整个过程就是一个简化版的Event Loop。此外回调函数执行时,一样会生成一个执行栈,在回调函数里面还有可能嵌套异步的函数,也就是说执行栈存在着嵌套。

也就是说node中的单线程是指js引擎只在惟一的主线程上运行,其余的异步操做,也是有独立的线程去执行,经过libv的Event Loop实现了相似于多线程的上下文切换以及线程池调度。线程是最小的进程,所以node也是单进程的。这样就解释了为何node是单线程和单进程的。

2、node中的child_process模块实现多进程

  node是单进程的,必然存在一个问题,就是没法充分利用cpu等资源。node提供了child_process模块来实现子进程,从而实现一个广义上的多进程的模式。经过child_process模块,能够实现1个主进程,多个子进程的模式,主进程称为master进程,子进程又称工做进程。在子进程中不只能够调用其余node程序,也能够执行非node程序以及shell命令等等,执行完子进程后,以流或者回调的形式返回。

一、child_process模块提供的API

child_process提供了4个方法,用于新建子进程,这4个方法分别为spawn、execFile、exec和fork。全部的方法都是异步的,能够用一张图来描述这4个方法的区别。

default

上图能够展现出这4个方法的区别,咱们也能够简要介绍这4中方法的不一样。

  • spawn : 子进程中执行的是非node程序,提供一组参数后,执行的结果以流的形式返回。
  • execFile:子进程中执行的是非node程序,提供一组参数后,执行的结果以回调的形式返回。
  • exec:子进程执行的是非node程序,传入一串shell命令,执行后结果以回调的形式返回,与execFile
    不一样的是exec能够直接执行一串shell命令。
  • fork:子进程执行的是node程序,提供一组参数后,执行的结果以流的形式返回,与spawn不一样,fork生成的子进程只能执行node应用。接下来的小节将具体的介绍这一些方法。

二、execFile和exec

咱们首先比较execFile和exec的区别,这两个方法的相同点:

执行的是非node应用,且执行后的结果以回调函数的形式返回。

不一样点是:

exec是直接执行的一段shell命令,而execFile是执行的一个应用

举例来讲,echo是UNIX系统的一个自带命令,咱们直接能够在命令行执行:

echo hello world

结果,在命令行中会打印出hello world.

(1) 经过exec来实现

新建一个main.js文件中,若是要使用exec方法,那么则在该文件中写入:

let cp=require('child_process');
cp.exec('echo hello world',function(err,stdout){
  console.log(stdout);
});

执行这个main.js,结果会输出hello world。咱们发现exec的第一个参数,跟shell命令彻底类似。

(2)经过execFile来实现

let cp=require('child_process');
cp.execFile('echo',['hello','world'],function(err,stdout){
   console.log(stdout);
});

execFile相似于执行了名为echo的应用,而后传入参数。execFlie会在process.env.PATH的路径中依次寻找是否有名为'echo'的应用,找到后就会执行。默认的process.env.PATH路径中包含了'usr/local/bin',而这个'usr/local/bin'目录中就存在了这个名为'echo'的程序,传入hello和world两个参数,执行后返回。

(3)安全性分析

像exec那样,能够直接执行一段shell是极为不安全的,好比有这么一段shell:

echo hello world;rm -rf

经过exec是能够直接执行的,rm -rf会删除当前目录下的文件。exec正如命令行同样,执行的等级很高,执行后会出现安全性的问题,而execFile不一样:

execFile('echo',['hello','world',';rm -rf'])

在传入参数的同时,会检测传入实参执行的安全性,若是存在安全性问题,会抛出异常。除了execFile外,spawn和fork也都不能直接执行shell,所以安全性较高。

三、spawn

spawn一样是用于执行非node应用,且不能直接执行shell,与execFile相比,spawn执行应用后的结果并非执行完成后,一次性的输出的,而是以流的形式输出。对于大批量的数据输出,经过流的形式能够介绍内存的使用。

咱们用一个文件的排序和去重来举例:

default

上述图片示意图中,首先读取的input.txt文件中有acba未经排序的文字,经过sort程序后能够实现排序功能,输出为aabc,最后经过uniq程序能够去重,获得abc。咱们能够用spawn流形式的输入输出来实现上述功能:

let cp=require('child_process');
let cat=cp.spawn('cat',['input.txt']);
let sort=cp.spawn('sort');
let uniq=cp.spawn('uniq');

cat.stdout.pipe(sort.stdin);
sort.stdout.pipe(uniq.stdin);
uniq.stdout.pipe(process.stdout);
console.log(process.stdout);

执行后,最后的结果将输入到process.stdout中。若是input.txt这个文件较大,那么以流的形式输入输出能够明显减少内存的占用,经过设置缓冲区的形式,减少内存占用的同时也能够提升输入输出的效率。

四、fork

在javascript中,在处理大量计算的任务方面,HTML里面经过web work来实现,使得任务脱离了主线程。在node中使用了一种内置于父进程和子进程之间的通讯来处理该问题,下降了大数据运行的压力。node中提供了fork方法,经过fork方法在单独的进程中执行node程序,而且经过父子间的通讯,子进程接受父进程的信息,并将执行后的结果返回给父进程。

使用fork方法,能够在父进程和子进程之间开放一个IPC通道,使得不一样的node进程间能够进行消息通讯。

在子进程中:

经过process.on('message')和process.send()的机制来接收和发送消息。

在父进程中:

经过child.on('message')和process.send()的机制来接收和发送消息。

具体例子,在child.js中:

process.on('message',function(msg){
   process.send(msg)
})

在parent.js中:

let cp=require('child_process');
let child=cp.fork('./child');
child.on('message',function(msg){
  console.log('got a message is',msg);
});
child.send('hello world');

执行parent.js会在命令行输出:got a message is hello world

中断父子间通讯的方式,能够经过在父进程中调用:

child.disconnect()

来实现断开父子间IPC通讯。

五、同步执行的子进程

exec、execFile、spawn和fork执行的子进程都是默认异步的,子进程的运行不会阻塞主进程。除此以外,child_process模块一样也提供了execFileSync、spawnSync和execSync来实现同步的方式执行子进程。

3、node中的cluster模块

cluster意为集成,集成了两个方面,第一个方面就是集成了child_process.fork方法建立node子进程的方式,第二个方面就是集成了根据多核CPU建立子进程后,自动控制负载均衡的方式。

咱们从官网的例子来看:

const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
  console.log(`主进程 ${process.pid} 正在运行`);

  // 衍生工做进程。
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  cluster.on('exit', (worker, code, signal) => {
    console.log(`工做进程 ${worker.process.pid} 已退出`);
  });
} else {
  // 工做进程能够共享任何 TCP 链接。
  // 在本例子中,共享的是一个 HTTP 服务器。
  http.createServer((req, res) => {
    res.writeHead(200);
    res.end('你好世界\n');
  }).listen(8000);

  console.log(`工做进程 ${process.pid} 已启动`);
}

最后输出的结果为:

$ node server.js
主进程 3596 正在运行
工做进程 4324 已启动
工做进程 4520 已启动
工做进程 6056 已启动
工做进程 5644 已启动

咱们将master称为主进程,而worker进程称为工做进程,利用cluster模块,使用node封装好的API、IPC通道和调度机能够很是简单的建立包括一个master进程下HTTP代理服务器 + 多个worker进程多个HTTP应用服务器的架构。

总结

本文首先介绍了node的单线程和单进程模式,接着从单线程的缺陷触发,介绍了node中如何实现子进程的方法,对比了child_process模块中几种不一样的子进程生成方案,最后简单介绍了内置的能够实现子进程以及CPU进程负载均衡的内置集成模块cluster。

相关文章
相关标签/搜索