不系统学习NodeJs之进程线程

news2025/7/17 15:20:06

不系统学习NodeJs之进程线程

从工作期间断断续续接触Node,从去年的3月份、10月份、又到今年的3月份,终于又决定要再仔细看看Node的相关。
不系统学习的各种时期笔记&参考记录于此。

参考:
Node.js 中文网
一篇文章构建你的 NodeJS 知识体系
线下书:深入浅出node.js 著-朴灵

一、进程与线程

参考:
深入理解Node.js 进程与线程

疑问:

  1. 都说Node是单线程,为什么启动Node后可以看到线程池内是多线程的,为什么单线程的Node却能用child_process | cluster创建多进程 ?
  2. 父子进程之间的关系、通信。
  3. cluster和child_process什么关系,创建出来的子进程有什么区别

除上以外,网上找的相关的其他问题(面试可能会问)
Node.js是单线程吗?
Node.js 做耗时的计算时候,如何避免阻塞?
Node.js如何实现多进程的开启和关闭?
Node.js可以创建线程吗?
你们开发过程中如何实现进程守护的?
除了使用第三方模块,你们自己是否封装过一个多进程架构?

进程与线程

先从定义开始理解。

进程 Process是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础,进程是线程的容器。进程是资源分配的最小单位。
 
我们启动一个服务、运行一个实例,就是开一个服务进程,例如 Java 里的 JVM 本身就是一个进程,Node.js 里通过 node app.js 开启一个服务进程,多进程就是进程的复制(fork),fork 出来的每个进程都拥有自己的独立空间地址、数据栈,一个进程无法访问另外一个进程里定义的变量、数据结构,只有建立了 IPC 通信,进程之间才可数据共享。
 
线程是操作系统能够进行运算调度的最小单位,首先我们要清楚线程是隶属于进程的,被包含于进程之中。一个线程只能隶属于一个进程,但是一个进程是可以拥有多个线程的。
 
单线程就是一个进程只开一个线程
Javascript 就是属于单线程,程序顺序执行(这里暂且不提JS异步),可以想象一下队列,前面一个执行完之后,后面才可以执行,当你在使用单线程语言编码时切勿有过多耗时的同步操作,否则线程会造成阻塞,导致后续响应无法处理。你如果采用 Javascript 进行编码时候,请尽可能的利用Javascript异步操作的特性。

// 开启一个Node服务进程

const http = require('http');
const server = http.createServer();
server.listen(3000,()=>{
	process.title='测试进程';
    console.log('进程id',process.pid)
})

则创建Node进程如下:
在这里插入图片描述

Node中的进程与线程

Node.js 是 Javascript 在服务端的运行环境,构建在 chrome 的 V8 引擎之上,基于事件驱动、非阻塞I/O模型,充分利用操作系统提供的异步 I/O 进行多任务的执行,适合于 I/O 密集型的应用场景,因为异步,程序无需阻塞等待结果返回,而是基于回调通知的机制,原本同步模式等待的时间,则可以用来处理其它任务。因此Node可以应用于高并发场景。

科普:在 Web 服务器方面,著名的 Nginx 也是采用此模式(事件驱动),避免了多线程的线程创建、线程上下文切换的开销,Nginx 采用 C 语言进行编写,主要用来做高性能的 Web 服务器,不适合做业务。

在单核 CPU 系统之上我们采用 单进程 + 单线程 的模式来开发。在多核 CPU 系统之上,可以通过 child_process.fork 开启多个进程(Node.js 在 v0.8 版本之后新增了Cluster 来实现多进程架构) ,即 多进程 + 单线程 模式。注意:开启多进程不是为了解决高并发,主要是解决了单进程模式下 Node.js CPU 利用率不足的情况,充分利用多核 CPU 的性能。

在上面的例子中,不知大家是否有发现:开启了一个进程,却有9个线程,一直以来大家都说Node是单线程语言,这是否自相矛盾了呢?(这个问题困惑了我很久哈哈哈)

Node 中最核心的是 v8 引擎,在 Node 启动后,会创建 v8 的实例,这个实例是多线程的。

  • 主线程:编译、执行代码。
  • 编译/优化线程:在主线程执行的时候,可以优化代码。
  • 分析器线程:记录分析代码运行时间,为 Crankshaft 优化代码执行提供依据。
  • 垃圾回收的几个线程。

所以大家常说的 Node 是单线程的指的是 JavaScript 的执行是单线程的(开发者编写的代码运行在单线程环境中),但 Javascript 的宿主环境,无论是 Node 还是浏览器都是多线程的因为libuv中有线程池的概念存在的,libuv会通过类似线程池的实现来模拟不同操作系统的异步调用,这对开发者来说是不可见的。
(简单一句话解释:JavaScript在单线程中运行。但是其他node.js底层的、非Javascript的代码,是跑在多线程环境上的,内部完成I/O任务的是线程池,而线程池是多线程的。)

Node是如何做到异步I/O的呢? 主要靠: 事件循环,观察者,请求对象和I/O线程池。
《深入浅出node.js》朴灵

那又引出了我新的问题:事件循环是在JS线程上执行的,其他异步执行的任务又在哪里执行呢?
根据查询资料,得出如下结论:
异步的任务需要看它的类型,如果是IO任务或者其他libuv可执行的异步任务,会被丢到libuv管理的线程池中异步执行,之后再返回消息到事件循环中的队列去消费。但是非异步任务,如果一定要使用同步任务呢?(CPU密集型),则会产生阻塞,可以使用nextTick分片到下一个tick中,或者使用node提供的子进程服务,去利用其他的CPU内核执行任务。

参考:
Node.js 软肋之 CPU 密集型任务
nodejs单线程、异步事件的理解

worker子进程

*Node.js中的多进程和多线程
cluster 和 child_process 的使用场景

cluster的底层是child_process实现的,好像是在集群中有区别,但是我没看懂也没找到。cluster比起child_process实现了一些优化集成,cluster模块使用内置的负载均衡来更好地处理线程之间的压力(比child_process的负载均衡方式做了优化)

具体的上面的文章已经非常详细了,我只是做了一些简单记录:

一、创建

使用fork()创建子进程,一般子进程个数不超过CPU内核数。
cluster模块同样调用fork方法来创建子进程,该方法与child_process中的fork是同一个方法。

child_process

const childProcess = require('child_process')
const cpuNum = require('os').cpus().length

for (let i = 0; i < cpuNum; i++) {
    childProcess.fork('./worker.js')
}

console.log('Master: Hello world.')

cluster

cluster模块采用的是经典的主从模型,Cluster会创建一个master,然后根据你指定的数量复制出多个子进程,可以使用 cluster.isMaster 属性判断当前进程是master还是worker(工作进程)。由master进程来管理所有的子进程,主进程不负责具体的任务处理,主要工作是负责调度和管理。

开启多进程时候端口疑问讲解:如果多个Node进程监听同一个端口时会出现 Error:listen EADDRIUNS的错误,而cluster模块为什么可以让多个子进程监听同一个端口呢?
原因是master进程内部启动了一个TCP服务器,而真正监听端口的只有这个服务器,当来自前端的请求触发服务器的connection事件后,master会将对应的socket具柄发送给子进程。

const cluster = require('cluster')

if (cluster.isMaster) {
    /* master进程 */
    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: ' + signal)
        cluster.fork()
    })
} else {
    /* worker进程 */
    const net = require('net')
    net.createServer().on('connection', (socket) => {
        // 利用setTimeout模拟处理请求时的操作耗时
        setTimeout(() => {
            socket.end('Request handled by worker-' + process.pid)
        }, 10)
    }).listen(8080)
}

二、通信

child_process

child_process通信是通过on(‘message’)和send()来实现的。父进程中创建worker,直接在worker上挂载监听,子进程中通过process对象接口监听来自父进程的消息或者向父进程发送消息。

/src/master.js

const childProcess = require('child_process')
const worker = childProcess.fork('./worker.js')

worker.send('Hello world.')

worker.on('message', (msg) => {
    console.log('[Master] Received message from worker: ' + msg)
})
/src/worker.js

process.on('message', (msg) => {
    console.log('[Worker] Received message from master: ' + msg)
    process.send('Hi master.')
})
输出
$ node index.js

[Worker] Received message from master: Hello world.
[Master] Received message from worker: Hi master.

cluster

同child_process
写了一个简单的NodeJS实现的进程间通信的例子

cluster.setttings:配置集群参数对象
cluster.isMaster:判断是不是master节点
cluster.isWorker:判断是不是worker节点
Event: 'fork': 监听创建worker进程事件
Event: 'online': 监听worker创建成功事件
Event: 'listening': 监听worker向master状态事件
Event: 'disconnect': 监听worker断线事件
Event: 'exit': 监听worker退出事件
Event: 'setup': 监听setupMaster事件
cluster.setupMaster([settings]): 设置集群参数
cluster.fork([env]): 创建worker进程
cluster.disconnect([callback]): 关闭worket进程
cluster.worker: 获得当前的worker对象
cluster.workers: 获得集群中所有存活的worker对象
worker对象
worker的各种属性和函数:可以通过cluster.workers, cluster.worket获得。

worker.id: 进程ID号
worker.process: ChildProcess对象
worker.suicide: 在disconnect()后,判断worker是否自杀
worker.send(message, [sendHandle]): master给worker发送消息。注:worker给发master发送消息要用process.send(message)
worker.kill([signal='SIGTERM']): 杀死指定的worker,别名destory()
worker.disconnect(): 断开worker连接,让worker自杀
Event: 'message': 监听master和worker的message事件
Event: 'online': 监听指定的worker创建成功事件
Event: 'listening': 监听master向worker状态事件
Event: 'disconnect': 监听worker断线事件
Event: 'exit': 监听worker退出事件

负载均衡

上方链接有介绍,没整特别明白,不多记载了。

进程守护

每次启动 Node.js 程序都需要在命令窗口输入命令 node app.js 才能启动,但如果把命令窗口关闭则Node.js 程序服务就会立刻断掉。除此之外,当我们这个 Node.js 服务意外崩溃了就不能自动重启进程了。这些现象都不是我们想要看到的,所以需要通过某些方式来守护这个开启的进程,执行 node app.js 开启一个服务进程之后,我还可以在这个终端上做些别的事情,且不会相互影响。当出现问题可以自动重启。

worker进程可能因为某些异常情况而退出,为了提高集群的稳定性,master进程需要监听子进程的存活状态,当子进程退出之后,master进程要及时重启新的子进程。在Node中,子进程退出时,会在父进程中触发exit事件。父进程只需通过监听该事件便可知道子进程是否退出,并在退出的时候做出相应的处理。

// 例子

// 创建工作进程
let workers = []
let cur = 0
for (let i = 0; i < cpuNum; ++i) {
    const newProcess = childProcess.fork('./worker.js')
    workers.push(newProcess)
    console.log('Create worker-' + workers[i].pid)

    // 工作进程退出后重启
    newProcess.on('exit', () => {
        console.log('Worker-' + newProcess.pid + ' exited')
        workers[i] = childProcess.fork('./worker.js')
        console.log('Create worker-' + childProcess.pid)
    })
}

或者使用第三方依赖实现进程守护。pm2 和 forever ,底层也都是通过上面讲的 child_process 模块和 cluster 模块 实现的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/395788.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

BIC高颜值在线绘图新增颜色集合本地存储功能

用户可以把自己选择的颜色集合存储在自己浏览器本地(鼠标滑过颜色名字可以显示具体代表哪些颜色和色号)&#xff0c;供同一个工具的多套数据使用或者在不同工具之间共享颜色集合的使用&#xff0c;统一多张图一致的配色。最新访问地址&#xff1a;https://www.bic.ac.cn/BIC/ &…

JavaWeb—CSS

目录 &#xff11;、CSS 技术 &#xff11;.&#xff11;、CSS 技术介绍 &#xff11;.&#xff12;、CSS 语法规则&#xff1a; &#xff11;.&#xff13;、CSS 和 HTML 的结合方式 &#xff11;.&#xff13;.&#xff11;、第一种&#xff1a; &#xff11;.&#xf…

Iterator 迭代器

迭代器 为了兼顾 各个子类的特性 实现无差别可以 访问数据 举个例子 遍历数组和遍历链表 两者代码的写法不一样 为了实现 使用相同的代码 对不同的数据容器进行遍历 就出现了 迭代器 for语句的执行和 interator的实现息息相关 目的 访问各个类型 集合 的数据&#xff…

Spring源码解析-Spring 循环依赖

Spring源码解析简图&#xff1a; Spring 如何解决循环依赖&#xff0c;⽹上的资料很多&#xff0c;但是感觉写得好的极少&#xff0c;特别是源码解读⽅⾯&#xff0c;我就⾃⼰单独出⼀ 篇&#xff0c;这篇⽂章绝对肝&#xff01; 文章目录&#xff1a; 一. 基础知识 1.1 什么…

记录--elementui源码学习之仿写一个el-button

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 本篇文章记录仿写一个el-button组件细节&#xff0c;从而有助于大家更好理解饿了么ui对应组件具体工作细节。本文是elementui源码学习仿写系列的又一篇文章&#xff0c;后续空闲了会不断更新并仿写其他…

Unity iOS 无服务器做一个排行榜 GameCenter

排行榜需求解决方案一(嗯目前只有一)UnityEngine.SocialPlatformsiOS GameCenterAppStoreConnect配置Unity 调用(如果使用GameCenter系统的面板&#xff0c;看到这里就可以了&#xff09;坑(需要获取数据做自定义面板的看这里)iOS代码Unity 代码吐槽需求 需求&#xff1a;接入…

某某游戏加密坐标分析

这个游戏里面坐标有很多种存放方式。 例如明文存放的DOUBLE&#xff0c;加密的各种类型。 我们不知道哪一个对于我们是有用的,哪一些只是辅助UI或则掉到LUA虚拟机坑里的数据。 那就根据作用大小来决定,一一尝试吧。 最好去找修改之后有效果的地址&#xff0c;当然只是本地&…

记一次影视cms黑盒CSRF-RCE

俗话说得好&#xff0c;思路才是最重要&#xff0c;本文章主要提供思路&#xff0c;各位师傅在挖掘漏洞的时候说不定也能碰到类似的点1.思路&#xff1a;当我们在找可以构建csrf的时候&#xff0c;多找找可以提交上传图片的&#xff0c;部分是可以自由构建url如图&#xff1a;漏…

Python数据分析案例20——我国家庭资产影响因素分析

本次案例较为简单&#xff0c;符合人文社科、经济学管理学等专业本科生适用。 本文的数据来源于中国家庭金融调查&#xff08;China Household Finance Survey&#xff0c;CHFS&#xff09;是西南财经大学中国家庭金融调查与研究中心&#xff08;下称中心&#xff09;在全国范围…

后端快速上手Vue+axios

文章目录前言vue基础1.el:挂载点2.data:数据对象vue常见指令vue生命周期axiosvueaxios前言 面向后端人员&#xff0c;旨在快速熟悉Vue框架&#xff0c;更详细的以后再总结 &#xff08;1&#xff09;Vue的特性&#xff1a; JavaScript框架简化Dom操作响应式数据驱动 &#…

JWT详细介绍使用

一、JWT介绍 JWT是JSON Web Token的缩写&#xff0c;即JSON Web令牌&#xff0c;是一种自包含令牌。 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。 JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息&#xff0c;以便于从资源服务…

【经验分享】电路板上电就挂?新手工程师该怎么检查PCB?

小伙伴们有没有经历过辛辛苦苦&#xff0c;加班加点设计的PCB&#xff0c;终于搞定下单制板。接下来焦急并且忐忑地等待PCB板到货&#xff0c;焊接&#xff0c;验证&#xff0c;一上电&#xff0c;结果直接挂了... 连忙赶紧排查&#xff0c;找问题。最终发现&#xff0c;是打过…

学习笔记:基于SpringBoot的牛客网社区项目实现(二)之Spring MVC入门

1.1 函数的返回值为空&#xff0c;因为可以使用response对象向浏览器返回数据。声明了request对象和response对象&#xff0c;dispatcherservlet自动将这两个对象传入 RequestMapping("/http")public void http(HttpServletRequest request, HttpServletResponse re…

ReentranLock(可重入锁)

一、ReentranLock ReentranLock属于JUC并发工具包下的类&#xff0c;相当于 synchronized具备如下特点 ● 可中断 ● 可以设置超时时间 ● 可以设置为公平锁&#xff08;防止线程出现饥饿的情况&#xff09; ● 支持多个条件变量 与 synchronized一样&#xff0c;都支持可重…

浅析 SplitChunksPlugin 及代码分割的意义

本文作者为 360 奇舞团前端开发工程师起因有同事分享webpack的代码分割&#xff0c;其中提到了SplitChunksPlugin&#xff0c;对于文档上的描述大家有着不一样的理解&#xff0c;所以打算探究一下。Q&#xff1a;什么是 SplitChunksPlugin&#xff1f;SplitChunksPlugin 是用来…

Python所有方向的入门和进阶路线,20年老师傅告诉你方法

干了20多年程序员&#xff0c;对于Python研究一直没停过&#xff0c;这几天把我自己对Python的认知和经验&#xff0c;再结合很多招聘网站上的技术要求&#xff0c;整理出了Python所有方向的学习路线图&#xff0c;基本上各个方向应该学什么&#xff0c;都在上面了&#xff0c;…

macOS 13.3 Beta 3 (22E5236f)发布

系统介绍3 月 8 日消息&#xff0c;苹果今日向 Mac 电脑用户推送了 macOS 13.3 开发者预览版 Beta 3 更新&#xff08;内部版本号&#xff1a;22E5236f&#xff09;&#xff0c;本次更新距离上次发布隔了 7 天。macOS Ventura 带来了台前调度、连续互通相机、FaceTime 通话接力…

文件预览kkFileView安装及使用

1 前言网页端一般会遇到各种文件&#xff0c;比如&#xff1a;txt、doc、docx、pdf、xml、xls、xlsx、ppt、pptx、zip、png、jpg等等。有时候我们不想要把文件下载下来&#xff0c;而是想在线打开文件预览 &#xff0c;这个时候如果每一种格式都需要我们去写代码造轮子去实现预…

k8s pod调度总结

在Kubernetes平台上&#xff0c;我们很少会直接创建一个Pod&#xff0c;在大多数情况下会通过控制器完成对一组Pod副本的创建、调度 及全生命周期的自动控制任务&#xff0c;如&#xff1a;RC、Deployment、DaemonSet、Job 等。本文主要举例常见的Pod调度。1全自动调度功能&…

第二章:基础语法

第二章&#xff1a;基础语法 2.1&#xff1a;关键字和保留字 关键字 定义&#xff1a;被Java语言赋予了特殊含义&#xff0c;用做专门用途的字符串(单词) 特点&#xff1a;关键字中所有字母都为小写 分类&#xff1a; 用于定义数据类型的关键字 class、interface、enum、byt…