当前位置: 首页 > 后端技术 > Node.js

节点进程间通信

时间:2023-04-04 00:24:16 Node.js

作为一名合格的程序员,对进程和线程有所了解是很有必要的。本文将从以下几个方向进行梳理,试图了解发生了什么,为什么会发生:Process线程的概念和关系以及进程演进Inter-processcommunication了解底层基础,帮助上层保护进程和线程。进程和线程的概念和关系用户发出运行程序的命令后,就会产生一个进程。同一个程序可以产生多个进程(一对多关系),允许多个用户同时运行同一个程序而不会发生冲突。进程需要一些资源来完成它们的工作,如CPU使用时间、内存、文件、I/O设备等,而且它们是顺序执行的,即每个CPU核心在任何时候只能运行一个进程。进程和线程的区别:进程是计算机管理和运行程序的一种方式,进程可以包含一个或多个线程。一个线程可以理解为一个子进程。摘自维基百科,也就是说,一个进程是我们运行的程序代码和它占用的资源的总和,而线程是一个进程的最小执行单元,当然它也支持并发。可以说是把问题细化,分解成更小的问题,然后解决。并且进程中的线程共享进程资源,处于同一个地址空间,所以切换和通信成本比较小,进程可以理解为没有公包容器。但是如果需要进程间的通信,还需要一个通用的环境或媒介,这就是操作系统。进程演进我们的计算机有单核和多核,也有各种组合:因为单个进程是一个进程,所以一次只能处理一个事务,需要等待后续,这不利于体验。多个过程用于解决上述问题,但是如果请求很多,就会产生很多进程。本身开销就不是一个小问题,进程占用独立内存。这么多的响应都是过程,难免会有重复的状态和数据,会造成资源的浪费。多进程和多线程使用线程来处理事务而不是之前的进程,解决了高开销和资源浪费的问题。线程池也可以用来预先创建就绪线程,以减少创建和销毁线程的开销。但是一个cpu一次只能处理一个事务。像时间分片这样的调度线程会导致频繁的线程切换,非常耗时。单进程单线程类似于v8,基于事件驱动,有效避免了内存开销和上下文切换,只需要线程间通信在合适的时刻反馈事务结果。但是,当遇到一个计算量很大的事务时,就会阻塞后续任务的执行。像这样:单进程单线程(多进程架构)节点提供了cluster和child_process两个模块来创建进程,也就是我们常说的主(Master)从(Worker)模式。Master负责任务调度和管理Worker进程,Worker进行事务处理。进程间通信Node本身提供了cluster和child_process模块??来创建子进程。cluster.fork()本质上是child_process.fork()的上层实现。集群的好处是可以监听共享端口。否则,建议使用child_process。child_processchild_process提供了异步和同步两种运行方式,具体请参考文档。常见的异步方法有:.exec.execFile.fork.spawn除了fork的长驻进程外,其他方法会在子进程任务完成后以流的形式返回并销毁进程。异步方法会返回一个ChildProcess的实例,ChildProcess不能直接创建,只能返回。看几张图:比如有一个很长的循环。如果子进程没有启动,则循环后执行后续逻辑。我们可以把耗时循环放到子进程中,主进程会接受子进程的返回,不影响后续事物的处理。//主进程constexecFile=require('child_process').execFile;execFile('./child.js',[],(err,stdout,stderr)=>{if(err){console.log(err);return;}console.log(`stdout:${stdout}`);});console.log('用户事务处理');//子进程#!/usr/bin/envnodefor(leti=0;i<10000;i++){process.stdout.write(`${i}`);}对于fork,专门用来生产子进程,也可以说是主进程的一个拷贝,在返回的ChildProcess将内置一个额外的通信通道,即IPC通道,以允许消息在父进程和子进程之间传递,例如通过文件描述符。但是,由于创建了匿名通道,只有主进程可以与之通信,其他进程无法通信。但也有命名通道,详情见下一节。看一个简单的例子://parent.jsconstcp=require('child_process');constn=cp.fork(`${__dirname}/sub.js`);n.on('message',(m)=>{console.log('PARENTgotmessage:',m);});n.send({hello:'world'});//sub.jsprocess.on('message',(m)=>{console.log('CHILD收到消息:',m);});process.send({foo:'bar'});父进程通过fork返回的ChildProcess进行监听和发送通信,子进程通过全局变量process进行监听和发送通信。clustercluster本质上是通过child_process.fork创建子进程的,也可以帮助我们合理管理进程。constcluster=require('cluster');//判断是否是master进程if(cluster.isMaster){constcpuNum=require('os').cpus().length;for(leti=0;i{console.log('Createworker-'+worker.process.pid);});cluster.on('exit',(worker,code,signal)=>{console.log('[Master]worker'+worker.process.pid+'diedwithcode:'+code+',and'+signal);cluster.fork();//重启子进程});}else{constnet=require('net');net.createServer().on('connection',(socket)=>{setTimeout(()=>{socket.end('Requesthandledbyworker-'+process.pid);},10);}).listen(8989);}如果你细心的话,你可能会发现多个子进程都在监听同一个端口,所以不会有EADDRIUNS?其实真正监听端口的并不是主进程。当前端请求到达时,它会将句柄发送给子进程。了解底层基础,帮助上层应用进程间通信(IPC)大致有几种:匿名管道命名管道信号量消息队列信号共享内存套接字从技术上可以分为以下四种:消息传递(管道、FIFO、消息队列)同步(互斥、条件变量、读写锁等)共享内存(匿名、命名)远程过程调用文件描述符?在linux中,一切皆文件,linux会给每个文件分配一个id。这个id是文件描述符,指针也是文件描述符的一种。这很容易理解,但我们可以更深入。一个进程启动后,会在内核空间(虚拟空间的一部分)中创建一个PCB控制块。PCB内部有一个文件描述符表,记录了当前进程的所有可用数据。文件描述符(即当前进程所有打开的文件)。除了维护文件描述符表外,系统还需要维护打开文件表(Openfiletable)和索引节点表(i-nodetable)。打开文件表(Openfiletable)包含文件偏移量、状态标志、i节点表指针等信息i节点表(i-nodetable)包含文件类型、文件大小、时间戳、文件锁等信息等文件描述符不是一对一的,它可以:同一个进程的不同文件描述符指向同一个文件不同进程可以有相同的文件描述符(比如fork的子进程有相同的文件描述符为父进程,或者不同进程打开同一个文件)不同进程的同一个文件描述符也可以指向不同的文件不同进程的不同文件描述符也可以指向同一个文件上面提到了很多实现进程间通信的方式以上,那么什么是节点进程间通信呢?基于它?nodeIPC通过管道技术加事件循环进行通信。管道技术在windows下是通过namedpipe实现的,在*nix系统下是通过UnixDomainsocket实现的。它为我们提供了简单的消息事件和发送方法。那条管道是什么?管道实际上是在内核中开辟了一个缓冲区,缓冲区有读端和写端,通过两个文件描述符传递给用户程序,一个指向读端,一个指向写端口,然后缓冲区存储不同进程之间的数据。编写内容,针对不同的进程读取内容,进而达到通信的目的。管道又分为匿名管道和命名管道。匿名管道常用于进程fork出子进程,只能与相关进程通信时,而命名管道则允许非相关进程通信。其实从本质上讲,进程间通信就是利用内核来管理一块内存。不同的进程可以读写这段内容,然后相互通信。当然,这说起来容易做起来难。有兴趣的朋友可以自己研究。进程保护可以使用集群建立主从进程架构。master进程对子进程进行调度、管理和分发任务,并在子进程挂掉或断开连接后重新启动。pm2是对集群的一种封装,提供:内部traitor负载均衡、后台运行、关机、重载、Ubuntu和CentOS的启动脚本、停止不稳定进程、控制台检测、良好的可视化界面、具体原理和细节,分析之后。文中如有错误,请指出,我会及时更新。希望读者从中吸取教训。部分图片来自网络,引用链接进程、线程、协程文件描述符IPCIPC2,侵权即删