node进程间通信

安国
• 阅读 1680

作为一名合格的程序猿/媛,对于进程、线程还是有必要了解一点的,本文将从下面几个方向进行梳理,尽量做到知其然并知其所以然:

  • 进程和线程的概念和关系
  • 进程演进
  • 进程间通信
  • 理解底层基础,助力上层应用
  • 进程保护

进程和线程的概念和关系

用户下达运行程序的命令后,就会产生进程。同一程序可产生多个进程(一对多关系),以允许同时有多位用户运行同一程序,却不会相冲突。

进程需要一些资源才能完成工作,如CPU使用时间、存储器、文件以及I/O设备,且为依序逐一进行,也就是每个CPU核心任何时间内仅能运行一项进程。

进程与线程的区别:进程是计算机管理运行程序的一种方式,一个进程下可包含一个或者多个线程。线程可以理解为子进程。

摘自wiki百科

也就是说,进程是我们运行的程序代码和占用的资源总和,线程是进程的最小执行单位,当然也支持并发。可以说是把问题细化,分成一个个更小的问题,进而得以解决。

并且进程内的线程是共享进程资源的,处于同一地址空间,所以切换和通信相对成本小,而进程可以理解为没有公共的包裹容器

但是如果进程间需要通信的话,也需要一个公共环境或者一个媒介,这个就是操作系统。

进程演进

我们的计算机有单核的、多核的,也有多种的组合方式:

  1. 单进程

因为是一个进程,所以某一时刻只能处理一个事务,后续需要等待,体验不好

  1. 多进程

为了解决上面的问题,但是如果有很多请求的话,会产生很多进程,开销本身就是一个不小的问题,而进程占据独立的内存,这么多响应是的进程难免会有重复的状态和数据,会造成资源浪费。

  1. 多进程多线程

由之前的进程处理事务,改成使用线程处理事务,解决了开销大,资源浪费的问题,还可以使用线程池,预先创建就绪线程,减少创建和销毁线程的开销。

但是一个cpu某一时刻只能处理一个事务。像时间分片来调度线程的话,会导致线程切换频繁,是非常耗时的。

  1. 单进程单线程

类似也就是v8,基于事件驱动,有效的避免了内存开销和上下文切换,只需要线程间通信,即可在适当的时刻进行事务结果等的反馈。

但是遇到计算量很大的事务,会阻塞后续任务的执行。像这样:

node进程间通信

  1. 单进程单线程(多进程架构)

node提供了clusterchild_process两个模块进行进程的创建,也就是我们常说的主(Master)从(Worker)模式。Master负责任务调度和管理Worker进程,Worker进行事务处理。

node进程间通信

进程间通信

node本身提供了cluster和child_process模块创建子进程,本质上cluster.fork()是child_process.fork()的上层实现,cluster带来的好处是可以监听共享端口,否则建议使用child_process。

child_process

child_process提供了异步和同步的操作方法,具体可查看文档

常见的异步方法有:

  1. .exec
  2. .execFile
  3. .fork
  4. .spawn

除了fork出来的进程会长期驻存外,其他方式会在子进程任务完成后以流的方式返回并销毁进程。

异步方法会返回ChildProcess的实例,ChildProcess不能直接创建,只能返回。

来看几张图吧:

node进程间通信

node进程间通信

node进程间通信

举个例子

有一个很长很长的循环,如果不开启子进程,会等循环之后才能执行之后的逻辑。

我们可以将耗时的循环放到子进程中,主进程会接受子进程的返回,不影响后续事物的处理。

// 主进程
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)大概有这几种:

  • 匿名管道
  • 命名管道
  • 信号量
  • 消息队列
  • 信号
  • 共享内存
  • 套接字

从技术上划分又可以划分成以下四种:

  1. 消息传递(管道,FIFO,消息队列)
  2. 同步(互斥量,条件变量,读写锁等)
  3. 共享内存(匿名的,命名的)
  4. 远程过程调用

文件描述符是什么?

在linux中一切皆文件,linux会给每个文件分配一个id,这个id就是文件描述符,指针也是文件描述符的一种。这个很好理解,不过我们可以再往深了说,一个进程启动后,会在内核空间(虚拟空间的一部分)创建一个PCB控制块,PCB内部有一个文件描述符表,记录着当前进程所有可用的文件描述符(即当前进程所有打开的文件)。系统出了维护文件描述符表外,还需要维护打开文件表(Open file table)和i-node表(i-node table)。

文件打开表(Open file table)包含文件偏移量,状态标志,i-node表指针等信息

i-node表(i-node table)包括文件类型,文件大小,时间戳,文件锁等信息

文件描述符不是一对一的,它可以:

  1. 同一进程的不同文件描述符指向同一文件
  2. 不同进程可以拥有相同的文件描述符(比如fork出的子进程拥有和父进程一样的文件描述符,或者不同进程打开同一文件)
  3. 不同进程的同一文件描述符也可以指向不同的文件
  4. 不同进程的不同文件描述符也可以指向同一个文件

上面提及了很多可以实现进程间通信的方式,那node进程间通信是以什么为基础的呢?

nodeIPC通过管道技术 加 事件循环方式进行通信,管道技术在windows下由命名管道实现,在*nix系统则由Unix Domain socket实现,提供给我们的是简单的message事件和send方法。

那管道是什么呢?

管道实际上是在内核中开辟一块缓冲区,它有一个读端一个写端,并传给用户程序两个文件描述符,一个指向读端,一个指向写端口,然后该缓存区存储不同进程间写入的内容,并供不同进程读取内容,进而达到通信的目的。

管道又分为匿名管道和命名管道,匿名管道常见于一个进程fork出子进程,只能亲缘进程通信,而命名管道可以让非亲缘进程进行通信。

node进程间通信

其实本质上来说进程间通信是利用内核管理一块内存,不同进程可以读写这块内容,进而可以互相通信,当然,说起来简单,做起来难。有兴趣的朋友可以自行研究。

进程保护

可以用cluster建立主从进程架构,主进程调度管理和分发任务给子进程,并在子进程挂掉或断开连接后重启。

pm2是对cluster的一种封装,提供了:

  • 内奸负载均衡
  • 后台运行
  • 停机重载
  • 具有Ubuntu、CentOS的启动脚本
  • 停止不稳定的进程
  • 控制台检测
  • 有好的可视化界面

具体原理和细节以后有空再做分析。

文中若有错误的地方,欢迎指出,我会及时更新。希望读者借鉴的阅读。

部分图片来源网络,侵权立删

参考链接

进程、线程、协程

文件描述符

IPC

IPC2

点赞
收藏
评论区
推荐文章
Bill78 Bill78
4年前
python进程间通信
1python提供了多种进程通信的方式,主要Queue和Pipe这两种方式,Queue用于多个进程间实现通信,Pipe是两个进程的通信1.1Queue有两个方法:Put方法:以插入数据到队列中,他还有两
lucien-ma lucien-ma
4年前
什么是线程?什么是进程?
Java多线程基础进程和线程的概念应用程序是静态的概念,进程和线程是动态概念,有创建就有销毁,存在也是暂时的,不是永久性的。进程与线程的区别在于进程在运行时拥有独立的内存空间(每个进程所占有的内存都是独立的)多个线程是共享内存空间的,但是每个线程的执行时相互独立的,同时线程必须依赖于进程才能执行,单独的线程是无法执行的,由进程来控制多个线程的执行。
Wesley13 Wesley13
3年前
Android中的IPC通信(一)
IPC的概念  IPC:InterProcessCommunication,进程间通信。  短程通信:一般而言,应用程序的组件都是在一个进程内部里执行,这种在同一进程内的通信就称为短程通信。  远程通信:两个组件分别在不同的进程里执行,这种不同进程间组件的通信方式就被称为远程通信。IPC通信的效率
Wesley13 Wesley13
3年前
JUC并发编程之:简单概述(一)
JUC并发编程之:简单概述(一)内容概述:·进程和线程、并发和并行、同步和异步概念·如何查看和关闭进程·Java线程常用的类和方法一、概念:一、进程与线程1·进程·程序由指令和数据组成,但这些指令要运行,数据要读写,就
Stella981 Stella981
3年前
Android IPC(跨进程通信)之AIDL
AndroidIPC(跨进程通信)之AIDLIPC——跨进程通信,是指两个进程之间的数据交换过程。在说IPC的同时我们要知道什么是进程,什么是线程。线程是CPU调度的最小单元,进程可以理解为一个程序或者一个应用。一个进程中可以运行多个线程,而在Android程序中有一个主线程,也叫UI线程。在And
Wesley13 Wesley13
3年前
Java多线程系列(1)
本章主要内容有:1.线程进程的区别2.线程的生命周期3.Java内存模型原子性,可见性及有序性4.线程池及Java实现1.线程进程的区别线程:程序运行的最小单位进程:资源分配的最小单位一个进程可以有多个线程,多个线程共享进程里面的数据线程间通讯相对更加方便,进程间通讯需要通过IPC(
Stella981 Stella981
3年前
Linux环境进程间通信
Linux环境进程间通信(一)(https://www.oschina.net/action/GoToLink?urlhttp%3A%2F%2Fwww.ibm.com%2Fdeveloperworks%2Fcn%2Flinux%2Flipc%2Fpart1%2Findex.html)http://www.ibm.com/develope
Wesley13 Wesley13
3年前
Java并发编程:进程、线程、并行与并发
一谈到Java并发编程,我们一般就会联想起进程、线程、并行、并发等等概念。那么这些概念都代表什么呢?进程与线程有什么关系?并发与并行又是什么关系呢?01 进程与线程进程是指程序的一次动态执行过程,通常我们说计算机中正在执行的程序就是进程,每个程序都会对应着一个进程。一个进程包含了从代码加载到执行完成的一个完整过程,它是操作系
Stella981 Stella981
3年前
Linux进程间的通信方式和原理
进程的概念进程是操作系统的概念,每当我们执行一个程序时,对于操作系统来讲就创建了一个进程,在这个过程中,伴随着资源的分配和释放。可以认为进程是一个程序的一次执行过程。进程通信的概念进程用户空间是相互独立的,一般而言是不能相互访问的。但很多情况下进程间需要互相通信,来完成系统的某项功能。进程通过与内核及其
Wesley13 Wesley13
3年前
linux管道
管道是单向的字节流,它将某个进程的标准输出连接到另一个进程的标准输入。管道和有名管道是最早的进程间通信机制之一,管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。管道和有名管道的读写规则是在程序中应用它们的关键。管道相关的概念在linux中管道是通过指向同
Stella981 Stella981
3年前
Golang并发解读
进程与线程概念在面向进程设计的系统中,进程(process)是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。进程是程序(指令和数据)的真正运行实例。用户下达运行程序的命令后,就会产生进程。同一程序可产生多个进程(一对多关系),以允许同时有多位用户运行同一程序,却不会相冲突。线程(th