ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

Node.js进程与线程:下篇

2021-12-05 22:32:59  阅读:113  来源: 互联网

标签:Node process js cluster 线程 进程


  • Node.js是单线程吗?
  • Node.js 做耗时的计算时候,如何避免阻塞?
  • Node.js如何实现多进程的开启和关闭?
  • Node.js可以创建线程吗?

进程

进程Process是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础,进程是线程的容器。进程是资源分配的最小单位。我们启动一个服务、运行一个实例,就是开一个服务进程,例如Java 里的JVM本身就是一个进程,Node.js里通过node app.js开启一个服务进程,多进程就是进程的复制(fork),fork出来的每个进程都拥有自己的独立空间地址、数据栈,一个进程无法访问另外一个进程里定义的变量、数据结构,只有建立了IPC通信,进程之间才可数据共享。

// Node.js开启服务进程
const http = require('http');	
const server = http.createServer();	
server.listen(3000,()=>{	
    process.title='node process';	
    console.log('进程id', process.pid)	

线程

线程是操作系统能够进行运算调度的最小单位,首先我们要清楚线程是隶属于进程的,被包含于进程之中。一个线程只能隶属于一个进程,但是一个进程是可以拥有多个线程的,单线程就是一个进程只开一个线程。

Node.js中的进程与线程

Web业务开发中,如果你有高并发应用场景,那么Node.js会是你不错的选择。
在单核CPU系统上我们采用“单进程 + 单线程”的模式来开发,在多核CPU系统上,我们可以通过 child_process开启多个进程(Node.js在v0.8版本之后新增了Cluster来实现多进程架构),即“多进程 + 单线程”模式。
注意:开启多进程不是为了解决高并发,主要是解决了单进程模式下Node.js的CPU利用率不足的情况,以便能够充分利用多核CPU的性能。

1)process 模块
Node.js中的进程Process是一个全局对象,无需require便可直接使用,它给我们提供了当前进程中的一些相关信息。
部分常用到功能点:

  • process.env:环境变量,例如通过process.env.NODE_ENV来获取不同环境项目配置信息。
  • process.nextTick:这个在谈及到EventLoop时经常为会提到。
  • process.pid:获取当前进程id。
  • process.ppid:当前进程对应的父进程id。
  • process.cwd():获取当前进程的工作目录。
  • process.platform:获取当前进程运行的操作系统平台。
  • process.uptime():当前进程已运行时间,例如:pm2守护进程的uptime值。
  • 进程事件:process.on(‘uncaughtException’, cb) 捕获异常信息、process.on(‘exit’, cb)进程退出监听。
  • 三个标准流:process.stdout标准输出、process.stdin标准输入、process.stderr标准错误输出。
  • process.title:指定进程名称,有时候我们需要给进程指定一个名称。

2)child_process模块
child_process是Node.js的内置模块,常用函数:

  • child_process.spawn():适用于返回大量数据,例如图像处理、二进制数据处理。
  • child_process.exec():适用于小量数据,maxBuffer属性默认值为200 * 1024,超出这个默认值将会导致程序崩溃,数据量过大时可采用spawn。
  • child_process.execFile():类似于child_process.exec(),区别是不能通过shell来执行,不支持像I/O重定向和文件查找这样的行为。
  • child_process.fork():衍生新的进程,进程之间是相互独立的,每个进程都有自己的V8实例、内存,系统资源是有限的,不建议衍生太多的子进程出来,通长根据系统CPU 核心数来设置。
// fork开启子进程
const http = require('http');	
const fork = require('child_process').fork;	
const server = http.createServer((req, res) => {	
    if(req.url == '/compute'){	
        const compute = fork('./fork_compute.js');	
        compute.send('开启一个新的子进程');	
        // 当一个子进程使用process.send()发送消息时会触发'message'事件	
        compute.on('message', sum => {	
            res.end(`Sum is ${sum}`);	
            compute.kill();	
        });	
        // 子进程监听到一些错误消息退出	
        compute.on('close', (code, signal) => {	
            console.log(`收到close事件,子进程收到信号 ${signal} 而终止,退出码 ${code}`);	
            compute.kill();	
        })	
    }else{	
        res.end(`ok`);	
    }	
});	
server.listen(3000, 127.0.0.1, () => {	
    console.log(`server started at http://${127.0.0.1}:${3000}`);	
});
// 创建子进程拆分出来单独进行运算
const computation = () => {	
    let sum = 0;	
    console.info('计算开始');	
    console.time('计算耗时');	
    for (let i = 0; i < 1e10; i++) {	
        sum += i	
    };	
    console.info('计算结束');	
    console.timeEnd('计算耗时');	
    return sum;	
};	
process.on('message', msg => {	
    console.log(msg, 'process.pid', process.pid); // 子进程id	
    const sum = computation();	
    // 如果Node.js进程是通过进程间通信产生的,那么process.send()方法可以用来给父进程发送消息	
    process.send(sum);	
})

3)cluster模块
在这里插入图片描述
cluster模块调用fork()方法来创建子进程,该方法与child_process中的fork()是同一个方法。cluster模块采用的是经典的主从模型,Cluster会创建一个master,然后根据你指定的数量复制出多个子进程,可以使用 cluster.isMaster属性来判断当前进程是master还是worker(工作进程)。由master进程来管理所有的子进程,主进程不负责具体的任务处理,主要工作是负责调度和管理。
cluster模块使用内置的负载均衡来更好地处理线程之间的压力,该负载均衡使用了Round-robin算法(也被称之为循环算法)。当使用Round-robin调度策略时,master accepts()所有传入的连接请求,然后将相应的TCP请求处理发送给选中的工作进程(该方式仍然通过IPC来进行通信)。
如果多个Node.js进程监听同一个端口时会出现"Error: listen EADDRIUNS"的错误,而cluster模块为什么可以让多个子进程监听同一个端口呢?原因是master进程内部启动了一个TCP服务器,而真正监听端口的只有这个服务器,当来自前端的请求触发服务器的connection事件后,master会将对应的socket句柄发送给子进程。

// cluster 开启子进程
const http = require('http');	
const cluster = require('cluster');	
const numCPUs = require('os').cpus().length;	

if (cluster.isMaster) {	
    console.log('Master proces id is', process.pid);	
    // fork workers	
    for(let i = 0; i < numCPUs; ++i){	
        cluster.fork();	
    }
    cluster.on('exit', function(worker, code, signal) {	
        console.log('worker process died,id', worker.process.pid)	
    })	
} else {	
    // Worker可以共享同一个TCP连接	
    // 这里是一个http服务器	
    http.createServer(function(req, res) {	
        res.writeHead(200);	
        res.end('hello word');	
    }).listen(8000);	
}

4)child_process模块与cluster模块总结
无论是child_process模块还是cluster模块,都是为了解决Node.js实例在单线程运行时无法利用多核CPU的问题,核心就是父进程(即master进程)负责监听端口,接收到后将其分发给下面的worker进程。
cluster模块的一个弊端:cluster模块内部隐式地构建了TCP服务器,这对使用者确实简单和透明了许多,但是这种方式无法像使用child_process模块那样灵活,因为一个主进程只能管理一组相同的工作进程,而自行通过child_process模块来创建工作进程,一个主进程则可以控制多组进程,原因是child_process模块操作子进程时,可以隐式地创建多个TCP服务器。
在这里插入图片描述

Node.js单线程的误区

大家平时常说的Node.js是单线程,指的是JavaScript的执行是单线程的(开发者编写的代码运行在单线程环境中),但Javascript的宿主环境,无论是Node.js还是浏览器都是多线程的,因为libuv中是有线程池的概念存在的,libuv会通过类似线程池的实现来模拟不同操作系统的异步调用,这对开发者来说是不可见的,某些异步I/O会占用额外的线程。
Node.js中最核心的是v8引擎,在 Node.js启动后,会创建v8的实例,而这个实例是多线程的。

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

标签:Node,process,js,cluster,线程,进程
来源: https://blog.csdn.net/qq_43665821/article/details/121735937

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有