You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
constcluster=require("cluster");// 判断是否为主进程if(cluster.isMaster){constcpuNum=require("os").cpus().length;for(leti=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{constnet=require("net");net.createServer().on("connection",(socket)=>{setTimeout(()=>{socket.end("Request handled by worker-"+process.pid);},10);}).listen(8989);}
细心地你可能发现多个子进程监听了同一个端口,这样不会 EADDRIUNS 吗?
其实不然,真正监听端口的是主进程,当前端请求到达时,会将句柄发送给某个子进程。
二、进程间的通信
2.1 进程间通信分类
每个进程都有各自不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一个缓冲区,进程 A 把数据从用户空间拷贝到内核缓冲区,进程 B 再从该缓冲区把数据读走,内核提供的这种机制称为进程间通信。
扫描下方二维码,收藏关注,及时获取答案以及详细解析,同时可解锁800+道前端面试题。
一、进程和线程
用户下达运行程序的命令后,就会产生进程。同一程序可产生多个进程(一对多关系),以允许同时有多位用户运行同一程序,却不会相冲突。
进程需要一些资源才能完成工作,如 CPU 使用时间、存储器、文件以及 I/O 设备,且为依序逐一进行,也就是每个 CPU 核心任何时间内仅能运行一项进程。
进程与线程的区别:进程是计算机管理运行程序的一种方式,一个进程下可包含一个或者多个线程。
也就是说,进程是我们运行的程序代码和占用的资源总和,线程是进程的最小执行单位,当然也支持并发。可以说是把问题细化,分成一个个更小的问题,进而得以解决。
并且进程内的线程是共享进程资源的,处于同一地址空间,所以切换和通信相对成本小,而进程可以理解为没有公共的包裹容器。
但是如果进程间需要通信的话,也需要一个公共环境或者一个媒介,这个就是操作系统。
1.1 进程的演进
计算机有单核的、多核的,也有多种的组合方式:
因为是一个进程,所以某一时刻只能处理一个事务,后续需要等待,体验不好
为了解决上面的问题,但是如果有很多请求的话,会产生很多进程,开销本身就是一个不小的问题,而进程占据独立的内存,这么多响应使的进程难免会有重复的状态和数据,会造成资源浪费。
由之前的进程处理事务,改成使用线程处理事务,解决了开销大,资源浪费的问题,还可以使用线程池,预先创建就绪线程,减少创建和销毁线程的开销。
但是一个 cpu 某一时刻只能处理一个事务。像时间分片来调度线程的话,会导致线程切换频繁,是非常耗时的。
类似也就是 v8,基于事件驱动,有效的避免了内存开销和上下文切换,只需要线程间通信,即可在适当的时刻进行事务结果等的反馈。
但是遇到计算量很大的事务,会阻塞后续任务的执行。像这样:
Node 提供了 cluster 和 child_process 两个模块进行进程的创建,也就是我们常说的主(Master)从(Worker) 模式。Master 负责任务调度和管理 Worker 进程,Worker 进行事务处理。
1.2 进程间的通信
Node 本身提供了 cluster 和 child_process 模块创建子进程,本质上 cluster.fork() 是 child_process.fork()的上层实现,cluster 带来的好处是可以监听共享端口,否则建议使用 child_process。
child_process 提供了异步和同步的操作方法
常见的异步方法有:
除了 fork 出来的进程会长期驻存外,其他方式会在子进程任务完成后以流的方式返回并销毁进程。
异步方法会返回 ChildProcess 的实例,ChildProcess 不能直接创建,只能返回。
看个例子
有一个很长很长的循环,如果不开启子进程,会等循环之后才能执行之后的逻辑。
我们可以将耗时的循环放到子进程中,主进程会接受子进程的返回,不影响后续事物的处理。
而对于 fork,它是专门用来生产子进程的,也可以说是主进程的拷贝,返回的 ChildProcess 中会内置额外的通信通道,也就是 IPC 通道,允许消息在父子进程间传递,例如通过文件描述符,不过由于创建的是匿名通道,所以只有主进程可以与之通信,其他进程无法进行通信。但相对的还有命名通道
看个例子:
父进程通过 fork 返回的 ChildProcess 进行通信的监听和发送,子进程通过全局变量 process 进行监听和发送。
cluster 本质上也是通过 child_process.fork 创建子进程,他还能帮我们合理的管理进程。
细心地你可能发现多个子进程监听了同一个端口,这样不会 EADDRIUNS 吗?
其实不然,真正监听端口的是主进程,当前端请求到达时,会将句柄发送给某个子进程。
二、进程间的通信
2.1 进程间通信分类
每个进程都有各自不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一个缓冲区,进程 A 把数据从用户空间拷贝到内核缓冲区,进程 B 再从该缓冲区把数据读走,内核提供的这种机制称为进程间通信。
进程间通信(IPC)大概有这种
从技术上又可以划分为以下四种:
上边提了很多实现进程间通信的方式,那 Node 进程间通信是以什么为基础的呢?
2.2 Node 进程间通信方式
NodeIPC 通过通道技术加事件循环方式进行通信,管道技术在 Windows 下由命名管道实现。在
*nix
系统则由 Unix Domain Socket 实现,提供给我们简单的 message 事件和 send 方法。这里提到了管道,那管道是什么?
2.3 什么是管道
管道实际上是在内核中开辟一块缓冲区,它有一个读端一个写端,并传给用户程序两个文件描述符,一个指向读端,一个指向写端口,然后该缓冲存储不同进程间写入的内容,并供不同进程读取内容,进而达到通信的目的。
管道又分为匿名管道和命名管道,匿名管道常见于一个进程 fork 出一个子进程,只能亲缘进程同喜,而命名管道可以让非亲缘进程进行通信。
其实本质上来说进程间通信是利用内核管理一块内存,不同进程可以读写这块内容,进而可以互相通信。
这里又提到了文件描述符,再来了解下文件描述符
2.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)包括文件类型,文件大小,时间戳,文件锁等信息
文件描述符不是一对一的,它可以:
The text was updated successfully, but these errors were encountered: