1.进程进程指的是一个正在进行的程序,也就是正在运行的程序。进程是操作系统进行资源分配和调度的最小单位,因此每个进程都有自己独立的虚拟地址空间,一般包括文本区(textregion)、数据区(dataregion)和栈区(stackregion)).文本区存放处理器执行的代码;数据区存放全局变量和常量;栈区存放函数的参数和函数中定义的局部变量;堆用于存储程序员创建的对象。2、进程模块节点提供进程模块,用于获取当前进程中的相关信息。这是一个全局对象,不需要require就可以直接使用。如:process.title,可以指定创建的进程的名称。process.pid,可以得到当前进程的id。process.ppid可以得到当前进程的父进程id。比如我们在命令行窗口(bash)中执行nodeserver.js,那么server.js程序进程的父进程id就是命令行窗口的进程idprocess。env,可以指定当前进程的环境变量,比如可以通过process.env.NODE_ENVprocess.cwd()来识别当前项目是开发环境还是生产环境,可以得到的工作目录当前进程process.platform,可以获取当前运行的进程操作系统平台process.nextTick(callback),可以为事件循环设置一个任务,node.js在进入之前会调用callback**process.uptime()next事件循环,可以获取当前进程的运行时间process.on(),可用于进程监控事件process.stdout、process.stdin、process.stderr,表示标准输出、标准输入、标准错误输出.console.log("hellonodeprocess.");process.title="我是一个节点进程";//设置进程名称console.log(`进程id为${process.pid},${process.uptime()}`);//打印进程id和运行时间当我们运行代码的时候,会发现电脑的活动监视器中并没有名为我是节点进程的进程,因为代码是很简单的同步代码,只是简单的输出,没有异步操作和等待操作,执行速度很快,输出完成后程序结束,从代码输出结果来看,进程运行时间为126ms,所以进程创建后代码被kill掉系统运行不久,所以我们看不到对应的进程名,即该进程是一个正在进行的程序。为了能够看到我们创建的进程,我们需要让程序运行更长时间,比如启动一个web服务器等待请求,开启一个setTimeout等,如下:consthttp=require("http");console.log("hellonodeprocess.");constserver=http.createServer();server.listen(3000,()=>{process.title="Mynodeprocess";//设置名称进程的console.log(`processidis${process.pid},${process.uptime()}`);//打印进程id和运行时间});这时候我们可以在活动监视器中看到刚才创建的那个名为我的节点进程的进程已经没有了。3.线程线程是程序执行的最小单位。它是进程中代码的不同执行路线。一个进程可以包含多个线程,所以进程是线程的容器。进程之间相互独立,但进程的资源可以在同一个进程下的线程之间共享。我们知道Node程序在运行时是单线程的,也就是说程序只有一条执行路径,所以它的代码会按顺序同步执行。如果在执行过程中遇到过多的耗时同步操作,那么node的线程就会被阻塞,无法处理后续的响应。因此,我们不应该在请求处理的回调中包含过多的耗时代码。虽然请求来了,node会马上把这个请求的回调加入到事件循环对应的队列中,但是事件循环中的代码执行也需要跑到主线程中去。也就是说,处理请求的回调函数必须在主线程中执行。如果回调中有大量耗时计算,后续请求会被阻塞。consthttp=require("http");console.log("hellonodeprocess.");constserver=http.createServer();constlongRuning=()=>{letresult=0;for(leti=0;i<3000;i++){for(letj=0;j<1000;j++){for(letk=0;k<1000;k++){结果=结果+i+j+k;}}}返回结果;};server.on("request",(req,res)=>{console.log("request");if(req.url==="/test"){console.log(`开始处理请求`);conststartTime=Date.now();constresult=longRuning();constendTime=Date.now();console.log(endTime-startTime);res.end(`resultis${result}`);}else{res.end("ok");}});server.listen(3000,()=>{process.title="Mynodeprocess";//设置名称processconsole.log(`进程id为${process.pid},${process.uptime()}`);//打印进程id和运行时间});可以看到节点服务器在监听请求的时候,会注册一个回调函数来处理请求,回调函数中需要进行一次耗时计算,大概需要15秒左右,所以主线程是阻止。四、node单线程的理解我们都知道node是单线程的。其实准确的说,node的主线程是单线程。例如,如果我们启动一个节点应用程序,我们可以看到这个进程包含8个线程,而不是一个线程。其中一个是主线程,另外7个是非主线程,因为我们的node应用启动后,会创建一个V8引擎实例,而这个实例是多线程的,主要如下:主线程:负责编译和执行JS代码;编译/优化线程:在主线程执行时,可以对代码进行优化;analyzer线程:记录和分析代码的运行时间,为Crankshaft优化代码执行提供依据;多个用于垃圾收集的线程;异步IO对线程数的影响?当我们通过fs模块读取文件时,会发现进程中的线程数立马增加了4个,因为node中的一些IO操作(DNS、FS)和一些CPU密集型计算(Zlib、Crypto)会启用node的线程池,node的线程池默认是4个,所以线程数会变成12个,比如:当然我们也可以手动修改默认线程池的个数,比如:process.env。UV_THREADPOOL_SIZE=10;//将节点默认线程池数量改为10个。一开始process.env中没有常量UV_THREADPOOL_SIZE。我们需要手动设置它才能读取它。经过上面的配置,我们再次运行代码,发现node进程中的线程数变成了18个,比如node虽然是单线程模型,但是它是基于事件驱动的,自然可以应对高并发请求,因为一旦有web请求过来,node可以监听事件,然后处理请求,在事件环中加入回调函数,其IO是异步非阻塞的。进行IO操作时无需等待,可以在进行IO操作的同时处理其他请求。因此,节点请求处理速度的关键是事件循环中回调函数的执行速度。如果回调中有大量的计算,会占用大量的CPU资源,导致无法继续处理后续的请求。基于以上,我们可以采用多进程的方式,将大量的计算放在子进程中,避免过度占用主线程资源。5.多进程当你的应用包含大量的计算时,会占用大量的CPU计算时间,CPU会阻塞在主线程的计算中,后续的请求也会阻塞,后面的请求需要等待上一个请求执行后,最后一个请求的响应时间将是所有请求时间的总和。如果请求太多,最后一个请求的响应时间会变得很恐怖。因此,我们可以充分利用多核CPU的特点,开启多个进程,让子进程进行耗时计算。需要注意的是,启用多进程并不是为了解决高并发,而是为了充分利用CPU。node应用本身就具有高并发可以处理,因为只要请求来了就会加入事件循环,不需要等待。Node通过child_process模块??或者cluster模块创建子进程。child_process模块child_process模块是node的内置模块,但是需要require才能使用。我们可以通过child_process模块??提供的fork()方法创建一个新的进程。由于系统资源有限,fork是一个独立的进程,这个进程有一个独立的全新的V8实例,至少需要10M内存,所以不建议派生太多的子进程,一般情况下可以根据cpu核数确定,1个CPU对应1个进程。在创建子进程时,我们需要将模块路径传递给fork()方法,然后它会返回创建的子进程。其实就是把一个模块的处理交给子进程。consthttp=require("http");constfork=require("child_process").fork;console.log("hellonodeprocess.");constserver=http.createServer();server.on("request",(req,res)=>{console.log("request");if(req.url==="/test"){console.log(`开始处理请求`);constcomputeProcess=fork("./fork_compute.js");//传入子进程需要处理的模块路径,创建一个子进程computeProcess.send("启动一个新的子进程完成耗时计算。");computeProcess.on("message",(result)=>{//子进程收到计算结果数据console.log(`子进程计算完成,结果为${result}`);res.end(`resultis${result}`);computeProcess.kill();//杀死子进程});computeProcess.on("close",(code,signal)=>{console.log(`子进程收到关闭事件和${signal}信号,退出码为${code}`);computeProcess.kill();});console.log("请求已处理");}else{res.end("确定");}});服务器。listen(3000,()=>{process.title="Mynodeprocess";//设置进程名称console.log(`进程id为${process.pid},${process.uptime()}`);//打印进程号和运行时间});新建fork_compute.js文件用于长计算constlongRuning=()=>{letresult=0;for(leti=0;i<5000;i++){for(letj=0;j<1000;j++){for(letk=0;k<1000;k++){结果=结果+i+j+k;}}}returnresult;};process.on("message",(msg)=>{//这里的进程指的是创建的子进程console.log(`收到子进程发送的msg,${msg},the子进程的id为${process.pid}`);constresult=longRuning();//子进程开始执行计算耗时代码process.send(result);//传递子进程对象发送结果,然后使用子进程对象在父进程中接收它});使用子进程后,我们发现在请求处理回调中并没有长时间的计算代码执行。而是创建子进程,子进程监听消息Event,这里没有耗时长的计算代码,所以请求处理的很快,等子进程处理完时间就可以返回结果-消耗计算。cluster模块cluster其实就是对child_process的一层封装。集群也通过fork()方法创建子进程,但是不需要传递任何参数,必须由主进程调用,因为它是根据主进程复制一个子进程。主进程该进程不负责处理请求,只负责绑定端口和调度工作任务。它通过内置的负载平衡管理子进程。consthttp=require("http");constcluster=require("集群");constcpuNums=require('os').cpus().length;//获取CPU核数if(cluster.isMaster){//如果是主进程process.title="我的节点进程";//设置主进程的名称for(leti=0;i
