Node.js的进程管理

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

  但是node的单线程使得在主线程不能进行CPU密集型操作,否则会阻塞主线程。对于CPU密集型操作,在node中通过child_process可以创建独立的子进程,父子进程通过IPC通信,子进程可以是外部应用也可以是node子程序,子进程执行后可以将结果返回给父进程。

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


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

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

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

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

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

node是真的单线程的吗?

提到node,我们就可以立刻想到单线程、异步IO、事件驱动等字眼。首先要明确的是node真的是单线程的吗,如果是单线程的,那么异步IO,以及定时事件(setTimeout、setInterval等)又是在哪里被执行的。

严格来说,node并不是单线程的。node中存在着多种线程,包括:

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

....

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

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

也就是说node中的单线程是指js引擎只在唯一的主线程上运行,其他的异步操作,也是有独立的线程去执行,通过libv的Event Loop实现了类似于多线程的上下文切换以及线程池调度。线程是最小的进程,因此node也是单进程的。这样就解释了为什么node是单线程和单进程的。

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

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

1、child_process模块提供的API

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

上图可以展示出这4个方法的区别,我们也可以简要介绍这4中方法的不同。

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

2、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,因此安全性较高。

3、spawn

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

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

上述图片示意图中,首先读取的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这个文件较大,那么以流的形式输入输出可以明显减小内存的占用,通过设置缓冲区的形式,减小内存占用的同时也可以提高输入输出的效率。

4、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通信。

5、同步执行的子进程

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

三、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。

来源:https://segmentfault.com/a/1190000016169207

nodejs中的子进程,深入解析child_process模块和cluster模块的更多相关文章

  1. Nodejs cluster模块深入探究

    由表及里 HTTP服务器用于响应来自客户端的请求,当客户端请求数逐渐增大时服务端的处理机制有多种,如tomcat的多线程.nginx的事件循环等.而对于node而言,由于其也采用事件循环和异步I/O机 ...

  2. Nodejs中cluster模块的多进程共享数据问题

    Nodejs中cluster模块的多进程共享数据问题 前述 nodejs在v0.6.x之后增加了一个模块cluster用于实现多进程,利用child_process模块来创建和管理进程,增加程序在多核 ...

  3. 在nodejs中引进模块要经历的步骤

    在nodejs中引入模块需要经历如下3个步骤 1.路径分析 2.文件定位 3.编译执行 在nodejs中模块分为两类,一类是nodejs提供的模块,称为核心模块,另一类的用户编写的模块,称为文件模块. ...

  4. 介绍nodejs中的path模块的几个方法

    webpack中常用的: var path = require('path') 是nodejs中的path模块,介绍一下webpack中常用的几个path模块的方法: 应用node环境的时候,这个pa ...

  5. nodejs中命令行中参数的获取和解析

    首先,假设有如下的命令行 node  abc.js arg1 arg2 arg3,现在想在abc.js中获取后面的参数arg1.arg2.arg3… var args = process.argv.s ...

  6. nodejs中相互引用(循环引用)的模块分析

    话不多少,直接上源码吧: modA.js: module.exports.test = 'A'; const modB = require('./05_modB'); console.log( 'mo ...

  7. node (02 CommonJs 和 Nodejs 中自定义模块)顺便讲讲module.exports和exports的区别 dependencies 与 devDependencies 之间的区别

    CommonJS 规范的提出,主要是为了弥补当前 JavaScript 没有标准的缺陷.它的终极目标就是:提供一个类似 Python,Ruby 和 Java 语言的标准库,而不只是停留在小脚本程序的阶 ...

  8. Node.js学习笔记(二) --- CommonJs和Nodejs 中自定义模块

    一. 什么是 CommonJs? JavaScript 是一个强大面向对象语言,它有很多快速高效的解释器. 然而, JavaScript标准定义的 API 是为了构建基于浏览器的应用程序.并没有制定一 ...

  9. Nodejs中关于模块的总结

    关于Nodejs中的模块 概念 Nodejs在ECMAScript的基础上扩展并封装了许多高级特性,如文件访问.网络访问等,使得Nodejs成为一个很好的Web开发平台.基于Nodejs这个平台将We ...

随机推荐

  1. DotnetBrowser入门教程-入门

    在.net core时代,web开发基本可以用.net core 2.0取代了.但是在传统领域,桌面开发仍然是不可以抛弃的,譬如: 1.用户需要和串口或者硬件打交道. 2.用户只想简单的安装好就使用, ...

  2. ofstream的使用方法

    ofstream的使用方法ofstream是从内存到硬盘,ifstream是从硬盘到内存,其实所谓的流缓冲就是内存空间;  在C 中,有一个stream这个类,所有的I/O都以这个“流”类为基础的,包 ...

  3. MapWindowPoints

    中文名 MapWindowPoints Windows CE 1.0及以上版本 头文件 winuser.h 库文件 user32.lib MapWindowPoints函数把相对于一个窗口的坐标空间的 ...

  4. Oracle递归操作

    需求:找出代理商中没有挂商家的代理商 简单SQL如下: select * from t_proxy tp where tp.id not in (SELECT tp.id as p_id FROM t ...

  5. [java面试]关于多态性的理解

    执行时多态性是面向对象程序设计代码重用的一个最强大机制.Java多态性的概念也能够被说成"一个接口.多个方法".Java实现执行时多态性的基础是动态方法调度,它是一种在执行时而不是 ...

  6. div下拉框(待改善)

    不说话,直接上代码,其中函数dealchose()没有实现,各位就不必纠结了 <%@ page language="java" import="java.util. ...

  7. C++的双重检查锁并不安全(转)

    一个典型的单例模式构建对象的双重检查锁如下: static Singleton * getSingleObject() { if(singleObject==NULL) { lock(); if(si ...

  8. U盘EFI分区删不掉怎么办

    方法/步骤 将U盘查到电脑上 点击[开始]找到并打开[Windows系统]的下拉按钮,找到[命令提示符] 在“命令提示符”上右键>[更多]>[以管理员身份运行]打开“管理员:命令提示符”窗 ...

  9. CPU核心电压与VID电压

    1.CPU核心电压与VID电压的区别 VID是CPU电压识别信号,CPU的工作电压就是由VID来定义的,CPU核心电压是CPU正常工作所需的电压 原理: (1)通常主板上用硬件VID确定BOOT VI ...

  10. Java获取系统属性及环境变量

    当程序中需要使用与操作系统相关的变量(例如:文件分隔符.换行符)时,Java提供了System类的静态方法getenv()和getProperty()用于返回系统相关的变量与属性,getenv方法返回 ...