它的优秀之处并不是原创,它的原创之处并不是优秀。编程
《深刻浅出Node》数组
本文章节以下图所示,阅读时间大约为10分钟~15分钟,图少字多,建议仔细阅读。多线程
在计算机资源中,I/O和CPU计算在硬件支持上是能够并行进行的。因此,同步编程中的I/O引发的阻塞致使后续任务(多是CPU计算,也多是其余I/O)的等待会形成资源的没必要要浪费。架构
说白了明明就是硬件支持,可是软件上不支持,就是浪费。因此要作的是尽最大可能不让阻塞形成不必的等待。并发
假设咱们如今拿到一组任务,其中既有I/O又有CPU计算,同时假设咱们的计算机是多核的但计算机资源有限的,为了减小上述的资源浪费状况你会怎么作?负载均衡
经过建立多个线程来分别执行CPU计算和I/O,这样CPU计算不会被I/O阻塞了。异步
它有以下的缺点:socket
首先它能够规避上述方案的缺点。函数
经过事件驱动的方式,当单线程执行CPU计算,I/O经过异步来进行调用和返回结果。这样也能使I/O不阻塞CPU计算。oop
可是它也有缺点:
咱们将上述描述的问题进行分解,梳理思路:
Node经过异步调用+维护I/O线程池+事件循环机制来减小或避免I/O阻塞CPU计算的时间。后面我逐步解释上述三者:
一图以蔽之。
这里咱们要把异步调用处理过程抽象到操做系统层面,咱们可知:异步调用是当应用程序发起I/O调用的时候,将调用信号发给操做系统,这时应用程序继续往下执行,直到操做系统完成任务以后,将数据返回,应用程序经过回调获取返回数据并在程序中执行相应的回调函数。
咱们将上述的操做系统进行剖析,其实内部是由Node维护了一个I/O线程池。
当JavaScript线程(JavaScript是单线程的我就不解释了吧)执行过程当中遇到了I/O任务的地方,会进行异步调用,封装参数和请求对象并将其放入线程池等待队列中等待执行。
当线程池有空余线程的时候,会让空余线程执行该I/O任务,执行完成以后,归还所占用的线程,同时咱们拿到了I/O任务的执行结果。
此时异步I/O进行的流程以下图所示:
IOCP是输入输出完成端口(Input/Output Completion Port,IOCP), 是支持多个同时发生的异步I/O操做的应用程序编程接口,是一个Windows内核对象。
异步任务完成了,那JavaScript线程是怎么知道的呢?
最暴力也是最直接的方式就是让CPU去轮询,即建立一个无限循环一直去检查I/O的完成状态。因此如今为了解决**问题T1(减小I/O阻塞CPU计算的时间。)而致使了问题T4(不要带来更多的额外消耗。)**的产生,由于CPU会花费额外的资源去处理状态判断和没必要要的“空转”。
这里咱们可抽象地理解为CPU去轮询线程池中的各线程的状态。
因此咱们要经过优化问题T4来尽量地减小消耗。
一个著名的优化思路就是设定一个不可能达到的理想状况,而后设计具体方法来无限逼近理想目标。这里咱们要优化问题T4使其趋近于问题T4不存在。
刚刚说了一直去检查I/O的状态是性能最低的方案(这叫read方案)。除此以外还有以下几种方案:
文件描述符是一个简单的整数,用以标明每个被进程所打开的文件和socket。不要以为1024很大了,在海量请求面前,真的是很小的数字。
下面咱们经过描述生产者/消费者模型来梳理基于epoll的整个方案:
线程池中各线程中I/O事件的完成是事件的生产者。
JavaScript线程中的事件的回调函数则是事件的消费者。
Step1: Node的轮询机制
在轮询I/O事件完成队列
时,发现为空(即没有任何线程完成I/O),则Node的轮询机制
进入休眠。
Step2: I/O线程池中有部分线程完成了,发送信号(操做系统完成)唤醒Node的轮询机制
,从I/O事件完成队列
里取出各完成的I/O对象,并执行相应的回调函数。
Step3: 若是在某次轮询时发现I/O事件完成队列
为空,则又进入休眠直到再次被唤醒。
上述的Node的轮询机制
则为事件循环即Event Loop,而I/O事件完成队列
也为咱们常说的事件观察者。
关于这部分的更多内容可细读《深刻浅出Node》第三章的3.3.2~3.3.5节。
通过事件循环,咱们能够得出整个异步I/O的过程了。如图所示:
结论:Node经过异步调用+维护I/O线程池+事件循环机制解决了T1问题(即减小I/O阻塞CPU计算的时间),同时也将T4问题(即不要带来更多的额外消耗)的影响降至最低,因为JavaScript执行部分始终是单线程的,因此也不存在须要锁机制和各状态同步,T2问题(即不要带来锁、状态同步等问题)也不存在了。
因此这里咱们能够得知,虽然JavaScript是单线程的,可是Node是多线程的,由于要维护一个I/O线程池啊。
这里咱们只讲了异步I/O的状况,固然还有非I/O的异步任务,好比setTimeout。若是你看懂了上述的事件循环,其实你就能够理解为setTimeout就是往定时器观察者(这里不是I/O观察者哦,观察者有多个)队列中插入一个事件而已,每次循环的时候判断是否到期,到期就执行。
值得注意的是:定时器观察者是一棵红黑树。
好了,最后咱们就要开始解决文章开头提到的T3问题了:
这里其实要解决的是单进程单核对于多核使用不足的问题。
废话很少说,Node用的是多进程架构,并采用Master-Worker的模式。,理想状态下每一个进程都分配到一个专属的CPU。
可是咱们要注意的是,建立工做进程(即子进程)的代价昂贵,须要至少30ms的启动时间和10MB的内存空间。因此必定要在开发的时候审慎对待。
搞清楚咱们的目的:多进程是为了利用多核CPU,而不是为了解决并发。
IPC可传递句柄,这让咱们能够实现多个进程监听同个端口,可实现负载均衡。具体参考《深刻浅出Node》第九章。
Node经过异步调用+维护I/O线程池+基于epoll的事件循环机制来实现的异步I/O,并经过Master-Worker的多进程架构来充分利用多核CPU
之后在面对这样的言论你能够说他们是错的了:
你能够给他们解释清楚,而后说: