node进程间通信
作为一名合格的程序猿/媛,对于进程、线程还是有必要了解一点的,本文将从下面几个方向进行梳理,尽量做到知其然并知其所以然:
- 进程和线程的概念和关系
- 进程演进
- 进程间通信
- 理解底层基础,助力上层应用
- 进程保护
进程和线程的概念和关系
用户下达运行程序的命令后,就会产生进程。同一程序可产生多个进程(一对多关系),以允许同时有多位用户运行同一程序,却不会相冲突。
进程需要一些资源才能完成工作,如CPU使用时间、存储器、文件以及I/O设备,且为依序逐一进行,也就是每个CPU核心任何时间内仅能运行一项进程。
进程与线程的区别:进程是计算机管理运行程序的一种方式,一个进程下可包含一个或者多个线程。线程可以理解为子进程。
摘自wiki百科
也就是说,进程是我们运行的程序代码和占用的资源总和,线程是进程的最小执行单位,当然也支持并发。可以说是把问题细化,分成一个个更小的问题,进而得以解决。
并且进程内的线程是共享进程资源的,处于同一地址空间,所以切换和通信相对成本小,而进程可以理解为没有公共的包裹容器。
但是如果进程间需要通信的话,也需要一个公共环境或者一个媒介,这个就是操作系统。
进程演进
我们的计算机有单核的、多核的,也有多种的组合方式:
- 单进程
因为是一个进程,所以某一时刻只能处理一个事务,后续需要等待,体验不好
- 多进程
为了解决上面的问题,但是如果有很多请求的话,会产生很多进程,开销本身就是一个不小的问题,而进程占据独立的内存,这么多响应是的进程难免会有重复的状态和数据,会造成资源浪费。
- 多进程多线程
由之前的进程处理事务,改成使用线程处理事务,解决了开销大,资源浪费的问题,还可以使用线程池,预先创建就绪线程,减少创建和销毁线程的开销。
但是一个cpu某一时刻只能处理一个事务。像时间分片来调度线程的话,会导致线程切换频繁,是非常耗时的。
- 单进程单线程
类似也就是v8,基于事件驱动,有效的避免了内存开销和上下文切换,只需要线程间通信,即可在适当的时刻进行事务结果等的反馈。
但是遇到计算量很大的事务,会阻塞后续任务的执行。像这样:
- 单进程单线程(多进程架构)
node提供了cluster和child_process两个模块进行进程的创建,也就是我们常说的主(Master)从(Worker)模式。Master负责任务调度和管理Worker进程,Worker进行事务处理。
进程间通信
node本身提供了cluster和child_process模块创建子进程,本质上cluster.fork()是child_process.fork()的上层实现,cluster带来的好处是可以监听共享端口,否则建议使用child_process。
child_process
child_process提供了异步和同步的操作方法,具体可查看文档。
常见的异步方法有:
除了fork出来的进程会长期驻存外,其他方式会在子进程任务完成后以流的方式返回并销毁进程。
异步方法会返回ChildProcess的实例,ChildProcess不能直接创建,只能返回。
来看几张图吧:
举个例子
有一个很长很长的循环,如果不开启子进程,会等循环之后才能执行之后的逻辑。
我们可以将耗时的循环放到子进程中,主进程会接受子进程的返回,不影响后续事物的处理。
// 主进程
const execFile = require('child_process').execFile;
execFile('./child.js', [], (err, stdout, stderr) => {
if (err) {
console.log(err);
return;
}
console.log(`stdout: ${stdout}`);
});
console.log('用户事务处理');
// 子进程
#!/usr/bin/env node
for (let i = 0; i < 10000; i++) {
process.stdout.write(`${i}`);
}
而对于fork,它是专门用来生产子进程的,也可以说是主进程的拷贝,返回的ChildProcess中会内置额外的通信通道,也就是IPC通道,允许消息在父子进程间传递,例如通过文件描述符,不过由于创建的是匿名通道,所以只有主进程可以与之通信,其他进程无法进行通信。但相对的还有命名通道,详见下一节。
看一个简单的例子:
//parent.js
const cp = require('child_process');
const n = cp.fork(`${__dirname}/sub.js`);
n.on('message', (m) => {
console.log('PARENT got message:', m);
});
n.send({ hello: 'world' });
//sub.js
process.on('message', (m) => {
console.log('CHILD got message:', m);
});
process.send({ foo: 'bar' });
父进程通过fork返回的ChildProcess进行通信的监听和发送,子进程通过全局变量process进行监听和发送。
cluster
cluster本质上也是通过child_process.fork创建子进程,他还能帮我们合理的管理进程。
const cluster = require('cluster');
// 判断是否为主进程
if (cluster.isMaster) {
const cpuNum = require('os').cpus().length;
for (let i = 0; i < cpuNum; ++i) {
cluster.fork();
}
cluster.on('online', (worker) => {
console.log('Create worker-' + worker.process.pid);
});
cluster.on('exit', (worker, code, signal) => {
console.log(
'[Master] worker ' +
worker.process.pid +
' died with code:' +
code +
', and' +
signal
);
cluster.fork(); // 重启子进程
});
} else {
const net = require('net');
net.createServer()
.on('connection', (socket) => {
setTimeout(() => {
socket.end('Request handled by worker-' + process.pid);
}, 10);
})
.listen(8989);
}
细心地你可能发现多个子进程监听了同一个端口,这样不会EADDRIUNS吗?
其实不然,真正监听端口的是主进程,当前端请求到达时,会将句柄发送给某个子进程。
理解底层基础,助力上层应用
进程间通信(IPC)大概有这几种:
- 匿名管道
- 命名管道
- 信号量
- 消息队列
- 信号
- 共享内存
- 套接字
从技术上划分又可以划分成以下四种:
- 消息传递(管道,FIFO,消息队列)
- 同步(互斥量,条件变量,读写锁等)
- 共享内存(匿名的,命名的)
- 远程过程调用
文件描述符是什么?
在linux中一切皆文件,linux会给每个文件分配一个id,这个id就是文件描述符,指针也是文件描述符的一种。这个很好理解,不过我们可以再往深了说,一个进程启动后,会在内核空间(虚拟空间的一部分)创建一个PCB控制块,PCB内部有一个文件描述符表,记录着当前进程所有可用的文件描述符(即当前进程所有打开的文件)。系统出了维护文件描述符表外,还需要维护打开文件表(Open file table)和i-node表(i-node table)。
文件打开表(Open file table)包含文件偏移量,状态标志,i-node表指针等信息
i-node表(i-node table)包括文件类型,文件大小,时间戳,文件锁等信息
文件描述符不是一对一的,它可以:
- 同一进程的不同文件描述符指向同一文件
- 不同进程可以拥有相同的文件描述符(比如fork出的子进程拥有和父进程一样的文件描述符,或者不同进程打开同一文件)
- 不同进程的同一文件描述符也可以指向不同的文件
- 不同进程的不同文件描述符也可以指向同一个文件
上面提及了很多可以实现进程间通信的方式,那node进程间通信是以什么为基础的呢?
nodeIPC通过管道技术 加 事件循环方式进行通信,管道技术在windows下由命名管道实现,在*nix系统则由Unix Domain socket实现,提供给我们的是简单的message事件和send方法。
那管道是什么呢?
管道实际上是在内核中开辟一块缓冲区,它有一个读端一个写端,并传给用户程序两个文件描述符,一个指向读端,一个指向写端口,然后该缓存区存储不同进程间写入的内容,并供不同进程读取内容,进而达到通信的目的。
管道又分为匿名管道和命名管道,匿名管道常见于一个进程fork出子进程,只能亲缘进程通信,而命名管道可以让非亲缘进程进行通信。
其实本质上来说进程间通信是利用内核管理一块内存,不同进程可以读写这块内容,进而可以互相通信,当然,说起来简单,做起来难。有兴趣的朋友可以自行研究。
进程保护
可以用cluster建立主从进程架构,主进程调度管理和分发任务给子进程,并在子进程挂掉或断开连接后重启。
pm2是对cluster的一种封装,提供了:
- 内奸负载均衡
- 后台运行
- 停机重载
- 具有Ubuntu、CentOS的启动脚本
- 停止不稳定的进程
- 控制台检测
- 有好的可视化界面
具体原理和细节以后有空再做分析。
文中若有错误的地方,欢迎指出,我会及时更新。希望读者借鉴的阅读。
部分图片来源网络,侵权立删
参考链接
node进程间通信的更多相关文章
- [转]Nodejs进程间通信
本文转自:http://www.cnblogs.com/rubyxie/articles/8949417.html 一.场景 Node运行在单线程下,但这并不意味着无法利用多核/多机下多进程的优势 事 ...
- babeljs源码
babel.min.js!function(e,t){"object"==typeof exports&&"object"==typeof mo ...
- node服务的监控预警系统架构
需求背景 目前node端的服务逐渐成熟,在不少公司内部也开始承担业务处理或者视图渲染工作.不同于个人开发的简单服务器,企业级的node服务要求更为苛刻: 高稳定性.高可靠性.鲁棒性以及直观的监控和报警 ...
- Edge.js:让.NET和Node.js代码比翼齐飞
通过Edge.js项目,你可以在一个进程中同时运行Node.js和.NET代码.在本文中,我将会论述这个项目背后的动机,并描述Edge.js提供的基本机制.随后将探讨一些Edge.js应用场景,它在这 ...
- Node.js的cluster模块——Web后端多进程服务
众所周知,Node.js是单线程的,一个单独的Node.js进程无法充分利用多核.Node.js从v0.6.0开始,新增cluster模块,让Node.js开发Web服务时,很方便的做到充分利用多核机 ...
- 写了一个简单的NodeJS实现的进程间通信的例子
1. cluster介绍 大家都知道nodejs是一个单进程单线程的服务器引擎,不管有多么的强大硬件,只能利用到单个CPU进行计算.所以,有人开发了第三方的cluster,让node可以利用多核CPU ...
- Node.js高级编程读书笔记 - 2 文件和进程处理
Outline 3 文件.进程.流和网络 3.1 查询和读写文件 3.2 创建和控制外部进程 3.3 读写数据流 3 文件.进程.流和网络 3.1 查询和读写文件 path 从Node 0.8起,pa ...
- Node.js网络编程
Node.js为javascript语言提供了一个在服务端运行的平台,它以其事件驱动,非阻塞I/O机制使得它本身非常适合开发运行在在分布式设备上的I/O密集型应用,分布式应用要求Node.js必须对网 ...
- [Node.js] Cluster,把多核用起来
原文地址: http://www.moye.me/?p=496 引子 众所周知,虽然Node的底层有一个IO线程池,但其应用层默认是单线程运行的,对于多核CPU环境来说,是一种资源的浪费. 所幸Nod ...
随机推荐
- FZU 1894 志愿者选拔
Problem 1894 志愿者选拔 Accept: 2308 Submit: 7003 Time Limit: 1500 mSec Memory Limit : 32768 KB Problem D ...
- andorid jar/库源码解析之Bolts
目录:andorid jar/库源码解析 Bolts: 作用: 用于链式执行跨线程代码,且传递数据 栗子: Task.call(new Callable<Boolean>() { @Ove ...
- awk调用外部程序
程序的功能很简单: 调用外部解密程序decoder,将文件第二列字段解密,然后写入新文件中. BEGIN { OFS = "\t" } { outputFileName = &qu ...
- Java λ表达式
目录 Java Lambda表达式 1. 函数式编程思想 2. 体验Lambda的更优写法 3. Lambda表达式的标准格式 4. 练习:使用Lambda表达式(无参无返回) 5. 练习:使用Lam ...
- 我的第一个UWP程序
1.为什么喜欢UWP 本人无悔入网易云音乐,各种设备上都少不了这个红色图标的软件 从win10发布,网易做了UWP版本的云音乐 应用轻巧.简洁.功能全,接着又下了许多UWP的应用 都给人不一样的感觉, ...
- 白话马尔科夫链蒙特卡罗方法(MCMC)
前言 你清茶园不是人待的地方! 里面的个个都是人才,说话又好听--就是我太菜了啥也听不懂,这次期中还考的贼**烂,太让人郁闷了. 最近课上讲这个马尔科夫链蒙特卡罗方法,我也学得一塌糊涂.这时我猛然想起 ...
- php对接金蝶系统
金蝶系统是强大的财务系统,可对公司的财务进行整理,所以有的时候需要去我php系统来对接金蝶系统,为金蝶系统生成各种单据.下面是php对接金蝶的流程. 各种方法已经封装好,直接可以调用就行了. 1.如果 ...
- C# 集合ArrayList :可以存储任何类型的数组,的基本用法
public void main() { //可以存储任何类型的数组 ArrayList alist = new ArrayList(); AddData(alist); RemoveData(ali ...
- 【SMB源码解析系列】——003.SMB游戏基本框架
前面有了解到RESET中断相关代码,结尾处通过一句jmp进入了无限循环,之后CPU将会在每一帧PUU进入VBlank状态时,接收NMI中断信号, 跳转至NMI代码处继续执行,直到遇见RTI指令时又返回 ...
- Python基础语法day_04——操作列表
day_04 遍历整个列表 我们创建列表时,需要输出整个列表,但是通常列表会很长,包含很多元素,当列表长度发生变化是,都必须修改代码.通过for循环,我们可以很轻易地输出整个列表. #遍历整个列表 创 ...