进程和线程
进程
- 进程(process): 是计算机中程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础
- 进程是线程的容器,进程是资源分配的最小单位,我们启动一个服务,运行一个实例,就是开一个服务进程
- Node通过node app.js就是开启了一个服务进程,多进程就是进程的复制(fork),fork出来的每个进程都拥有自己的独立空间地址,数据栈,一个进程无法访问另外一个进程里面定义的变量,数据结构,只有建立了IPC通信,进程之间才可以数据共享
node.js开启服务器进程的例子
const http = require('http')
const serve = http.createServer()
serve.listen(3000, () => {
process.title = '这是进程名称'
console.log('进程id', process.pid)
})
线程
- 线程:线程是操作系统能够进行运算调度的最小单位,首先我们要清楚线程是隶属于进程的,被包含于进程之中
- 一个线程只能隶属于一个进程,但是一个进程是可以拥有多个线程的
单线程
- 单线程就是一个进程只开启一个线程
- JavaScript就是属于单线程的,程序顺序执行 (先不异步的情况),可以想象一下队列,前面一个执行完这之后,后面才可以执行,当你在使用单线程语言编码时切勿有过多耗时间的同步操作,否者线程会造成阻塞,导致后续响应无法处理。
- 因此如果采用JavaScirpt进行编码的时候,应该尽可能的利用JavaScript异步操作的特性来处理
单线程的一些说明
- Node.js虽然是单线程模型,但是其基于事件驱动、异步非阻塞模式,可以应用于高并发的场景,避免了进程的创建,线程之间上下文切换所产生的资源开销
- 当你的项目中需要有大量的计算,CPU耗时的时候,要注意考虑开启多进程来完成了
- Node.js开发过程中,错误会引起整个应用退出,应用的健壮性值得考验,尤其是错误的异常抛出,以及进程守护是必须要做的
- 单线程无法利用多核CPU,但是后来Node.js提供了API以及一些第三方工具相应都得到了解决
多线程
-
一个进程运行时产生多个线程
- 比如腾讯管家,腾讯管家本身就是一个程序,即就是一个进程,它里面有很多个功能,即每个功能就对应一个线程,
- 如果按照单线程的话,如果你要清理垃圾和病毒查杀,必须完成其中一件事,才能做下一件事,这里面是有一个执行顺序的
- 如果按照多线程的话,我们可以在清理垃圾的时候,同时也可以进行病毒查杀,这就是同一时刻进行,没有执行上的先后顺序
-
多线程是为了同步完成多项任务,不是为了提高运行效率的,而是为了提高资源使用效率来提高系统的效率
Node.js中的进程和线程
- Node.js是JavaScript在服务端的运行环境,构建在chrome的V8引擎,基于事件驱动、非阻塞IO模型,充分利用操作系统提供的异步I/O运行多任务的执行,适合于I/O密集型的应用场景,因为异步,程序无需阻塞等到结果返回,而是基于回调通知的机制,原本同步模式等待的时间,则可以用来处理其他的任务
- 科普:在Web服务器访问,Nginx也是采用此模式 (事件驱动),避免了多线程的线程创建,线程上下文切换的开销,Nginx采用了C语言编写,主要用来做高性能的Web服务器,不适合做业务
- 在单核的CPU系统我们采用单进程+单线程的模式来开发,在多核的CPU系统,可以用过child_process.fork开启多个进程
- Node.js在v0.8版本之后新增了Cluster来实现多进程架构,即多进程+单线程模式。
- 注意:开启多进程不是为了解决高并发,主要是解决单进程模式下Node.js CPU利用率不足的情况,充分利用多核CPU的性能
Node.js中的进程
process模块
- Node.js中的进程是Process是一个全局对象,无需require直接使用,给我们提供了当前进程中的相关信息,官方文档提供了详细的说明,可以看一下process模块
- process.env :环境变量,例如通过process.env.NODE.ENV :获取不同环境项目的配置信息
- process.nextTick
- process.pid :获取当前进程id
- process.ppid: 当前进程对应的父进程
- process.cwd() :获取当前进程工作目录
- process.platform: 获取当前进程运行的操作系统平台
- process.uptime() :当前进程已运行时间,例如:pm2守护进程的uptime值
- 进程事件
- process.on('uncaughException',cb) 捕获异常信息
- process.on('exit',cb) 进程推出监听
- 三个标准流:
- process.stdout :标准输出
- process.stdin: 标准输入
- process.stderr :标准错误输出
- process.title: 指定进程名称,有的时候需要给进程指定一个名称
以上仅列举了部分常用到的功能点,出了Process之外,Node.js还提供了child.process模块用来对子进程进程操作,
Node.js进程创建
- 进程创建有多种方式,这里列举child_process模块和cluster模块进程说明
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核心数设置
CPU核心数说明: fork确实可以开启多个进程,但是不建议衍生出来太多的进程,cpu核心数的获取方式
const cpus = require('os').cpus();这里cpus返回一个对象数组,包含所安装的每个CPU/内核的信息,二者综合的数组
假设主机装有两个CPU,每个CPU有四个核,那么总核数就是8
fork开启子进程
- form开启子进程解决计算机耗时造成线程阻塞,在进程compute计算时创建子进程,子进程计算完成通过
send方法将结果发送给主进程,主进程通过message监听到信息后处理并退出
const http = require('http')
const fork = require('child_process').fork
const serve = 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事件,子进程收到信息')
compute.kill()
})
} else {
res.end(`ok`)
}
})
serve.listen(3000, () => {
console.log(`启动成功`)
})
单线程与多线程网络后端相比有哪些好处?
尽管 Node.js 是单线程的,但是大多数用于后端开发的编程语言都提供多线程来处理应用程序操作。
为什么单线程有利于后端开发?
- 开发人员更容易实现应用程序。我们的应用程序在生产过程中不会突然遇到意外的竞争条件。
- 单线程应用程序易于扩展。
- 它们可以毫不延迟地在一个时刻收到的大量用户请求提供服务。相比之下,当流量较大时,多线程后端必须等待线程池中的线程释放,才能为用户请求提供服务。利用 Node.js 的非阻塞特性,用户请求不会在单个线程上挂起太长时间(只有在操作不是 CPU 密集型时)。
进程间如何通信
可以使用内置的 process 对象来与其他进程进行通信。process 对象提供了许多用于与其他进程通信的方法
其中一些常用的方法如下:
process.send():用于将消息发送到父进程。 - process.on('message'):监听来自父进程的消息。 - process.exit():用于退出 Node.js 进程。