浅谈 nodejs 进程和线程
进程概念
进程是操作系统进行资源分配和调度的基本单位,是对操作系统上运行程序的一个抽象。每个进程拥有各自独立的内存空间,使得各个进程之间内存地址相互隔离。
这点很好理解,当运行一个程序时,首先会从磁盘中读取代码到内存中,由 CPU 去执行代码,运行过程中会涉及数据的存放、以及与外部设备交互等,这便涉及到程序对计算机资源的使用。那系统上存在那么多程序,且 CPU 只有一个,为了便于管理程序资源的使用,操作系统会把每个运行中的程序封装为一个实体,分配各自所需的资源,再根据调度算法切换执行。这里的实体,便是“进程”。
线程概念
当程序的功能越来越复杂,程序内部存在多种功能模块,比如网络请求、IO 操作等,我们希望能将应用程序分解成更细粒度、能准并行运行多个顺序执行实体,并且这些细粒度的实体能够共享进程的内存空间(程序代码、数据、内存空间等),因此就诞生了“线程”。
每个进程都有独立的代码和数据空间(程序上下文),程序之间切换会有较大开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有独立的运行栈和程序计算器,线程间的切换开销小。所以线程的创建、销毁、调度性能远优于进程。
线程是操作系统运算调度的最小单位,线程隶属于进程。一个线程只能隶属于一个进程,但一个进程能够拥有多个线程。
简而言之,进程负责分配和管理系统资源,线程负责 CPU 调度运算,也是 CPU 切换时间片的最小单位。
node.js 处理耗时操作
通过 fork 子进程处理耗时操作,子进程执行完耗时操作,再通过 send 方法将结果发送给主进程,主进程通过 message 监听信息后处理并退出。
通过child_process
模块创建子进程处理耗时操作的例子:
node.js 多进程架构
Node.js 通过node app.js
开启一个服务进程,多进程是通过进程的复制(fork),fork 出来的每个子进程都拥有各自独立的空间地址、数据栈,并且进程之间是相互独立,无法访问彼此的变量、数据结构。进程之间只有建立 IPC 通道才能共享数据。
这里采用多进程架构并非是用于解决高并发问题,而是处理单进程模式下 CPU 利用率不足的情况。核心是,父进程负责监听端口,接收到请求后将其分发给下面的 worker 进程。
以下是通过集群 cluster 创建多进程架构的简易例子:
cluster 集群采用的是经典的主从模型,会创建个主进程(master),然后根据指定数量创建子进程(worker),由 master 进程负责管理所有的子进程,主进程不处理具体的任务,主要工作是负责调度和管理。cluster 模块使用内置的负载均衡来处理线程之间的压力,该负载均衡使用了 Round-robin 算法(循环算法)。
cluster 模块开启的多个子进程监听同一个端口,为啥没报错误: Error:listen EADDRIUNS
呢?
这是由于 master 进程内部启动了一个 TCP 服务器,而真正监听端口的只有这个服务器,当来自前端的请求触发服务器的 connection 事件后,master 会将对应的 socket 句柄发送给子进程。
node.js 进程守护
通常通过命令行窗口执行命令node app.js
,启动一个 程序。但当命令行窗口关闭,服务会立刻断掉;或者当 Node 服务意外崩溃此时无法自动重启,这些情况都是不想看到的。因此需要对进程守护,出现意外情况重启。我们经常采用的是 pm2。
例子:指定生产环境,启动一个名为 app 的 node 服务
关于 pm2 其他命令,以及使用方法,详细看官网:
node.js 线程
node.js 是单线程指的是 JavaScript 的执行是单线程的,但 JavaScript 的宿主环境无论是 Node 还是浏览器都是多线程的。以下代码一行可以手动更改线程池默认数量。
总结
关于线程和进程的初步浅析就到这了,如果你对浏览器的多进程架构有想了解的,可以看看下面这篇文章:
版权声明: 本文为 InfoQ 作者【梁龙先森】的原创文章。
原文链接:【http://xie.infoq.cn/article/4eae8c8718983d7461052f54e】。文章转载请联系作者。
评论