探秘NodeJs·NodeJs的进程之谜
什么是进程
进程
是操作系统分配资源的最小单位。进程是应用程序的执行副本。
进程总体来说都是存在于内存
当中的,当然,当我们的设备内存不足的时候,操作系统可能会使用虚拟内存
技术,将非进行中的进程交换到硬盘当中,用以满足设备内存不足时进行中进程运行需求。
启动应用程序流程
应用程序(磁盘)
-> 进程(内存)
启动应用程序时操作系统会做些什么
在启动应用程序的过程中,操作系统将会给进程分配以下资源:
-
用户和组:是哪个用户、组启动的进程
-
目录资源:
-
进程的工作目录
进程工作目录即执行应用程序的的目录,也就是开启进程的目录。如下图,我们开启了一个
nodejs
的进程,那么,使用process.cwd()
就可以看到进程的工作目录。 -
进程的可见目录
-
-
文件资源
- 设备资源
- 网络资源
- 数据资源
- 代码资源
- ...
-
主线程
- 注意:进程是不能执行程序的。执行程序的是线程。操作系统会给每一个进程分配一个主线程,然后进程还可以分配其他线程
NodeJs的进程
Node.js
的进程也是进程,进程下有很多的线程。进程是Node
引擎执行的副本,是操作系统分配资源的最小单位。在NodeJs
中由很多线程一起完成执行程序的任务。
NodeJs
进程也有主线程NodeJs
进程也可以使用多线程(只不过不允许用户使用。因此网络上说NodeJs
是单线程实际上描述是不准确的,准确的说法是:"NodeJs本身是多线程的,但是只对用户开放单线程")NodeJs
进程有工作目录NodeJs
进程可以拥有文件资源- ...
总而言之,操作系统给进程的资源,NodeJs
进程也都能享受到,操作系统不给进程的,NodeJs
进程也没有。从这个层面上看,其实NodeJs
进程和Java
、Python
进程没什么区别。
NodeJs的线程模型
进程是分配资源(系统资源)的,线程是执行程序的
为什么不设计一个"既可以分配资源,又可以执行程序的模型呢?这样进程和线程的概念不就统一了吗?"
其实,在操作系统设计的早期,确实是这么设计的。但是随着时代和技术的发展,一个应用能干的事情越来越多,而创建和销毁进程是比较耗费时间和资源的,因此,才会在进程
下面设计了线程的概念,允许一个进程同时开启多个线程完成多项任务,而线程只需要执行程序相关的资源即可,创建和销毁的耗时和资源都比进程小很多。
线程之间本身是共享内存的,因此,创建一个新线程不需要像创建进程一样需要去申请内存空间,而是彼此共享同一个进程所申请的内存空间。
以下为线程
执行程序相关的资源:
- 程序计数器:用来标记程序执行到哪一行了
- 栈:用于存储执行程序的中间结果
- (虚拟)寄存器:用于辅助计算以及控制
形象的说:进程如果是秒级的,那么线程就是毫秒级的
那么,问一个问题:NodeJs是单进程吗?
其实,并没有单进程的说法,只有单线程,进程作为应用程序执行的副本,本身就是一个完整的整体,所以也没有多进程的说法。
再问一个问题:NodeJs是单线程吗?
NodeJs不是单线程的,在NodeJs内部有很多个线程。但用户执行的NodeJs程序只会运行在一个线程当中。
我们平时经常使用的定时器,如:setTimeout
,之所以能够不阻塞主线程的其他代码,异步执行,就是因为NodeJs
引擎为setTimeout
单独开启了一个新的线程用于处理其中的任务。
总结一下:线程的本质是抽象需要并行(concurrent)执行的任务,比如说:
- 浏览器的渲染
- 用户操作界面
- 发送网络请求
以上三个任务,其实我们完全可以让他们并行的执行,这样,在用户操作界面时,同时也可以进行浏览器渲染和发送网络请求的任务。
在NodeJs
中,每做一件需要操作系统支持的事情,就会使用一个单独的线程。比如说:
- 读取文件
- 发送网络请求
- 定时器
- ...
为了防止线程过多,在NodeJs
中有一个线程池用于处理需要操作系统支持的行为。
因此,其实我们可以把上面所说所有跟操作系统有关的事情,都开成一个"外部服务",而其他与操作系统无关的事情都是在一个线程中执行的。那么我们就可以把用户执行的程序看做是"单线程 "的。因为所有单线程无法完成的任务,都由NodeJs
引擎开启的其他线程处理了。
NodeJs为什么不给用户提供多线程能力
NodeJs
是IO密集型
的应用,我们经常在做的事情都是在读取写入文件或请求发送接口。我们不会用NodeJs
去做一些计算密集型
的任务,如:图像识别、神经网络计算等。即使目前市面上存在的一些用于神经网络计算的js
框架,也仅仅是作为连接桥连接底层C
或C++
之类其他语言写的核心服务。因此,NodeJs
其实并不需要给用户提供多线程的能力。
NodeJs的事件循环模型
我们都知道,在浏览器中也存在事件循环的概念,那么,浏览器中的事件循环跟NodeJs
中的事件循环有什么区别吗?
我们先来看一下NodeJs
中事件循环的示意图:
以下为浏览器事件循环的示意图:
我们可看到,两者本质上并没有什么区别。
那么,**事件循环(Event Loop)**到底是什么呢?
- 它是一个单线程的程序(用户程序+Event Loop是一个单线程程序,而涉及操作系统的功能则会开一个新的线程处理)
- 它是驱动 javascript 程序执行的动力源
Event Loop 赋予了原本单线程的用户程序并发的能力,它将单线程的执行拆分了很多任务
我们通常将用户程序称为:主栈程序(Main Stack),本质上,事件循环和用户程序在同一个线程中执行,因此也是发生在主栈上。
而定时器、网络请求、数据库操作、文件操作等都是发生在线程池上的。
宏任务与微任务
浏览器会先执行主栈任务,当主栈没有有要执行的任务时,会进入消息循环,有任务先执行宏任务,任何一个宏任务执行完都检查有没有微任务。
总体来说,宏任务是由宿主发起的,而微任务使用JavaScript发起的