Node事件循环

发布于:2024-04-08 ⋅ 阅读:(106) ⋅ 点赞:(0)

1.进程与线程

        进程是CPU资源分配的最小单位,而线程是CPU调度的最小单位

        进程(工厂)

                有单独的属于自己的工厂资源

        线程(工人)

                多个工人在一个工厂中协作工作,工厂与工人是1:n的关系。也就是说一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线

        工厂的空间是工人们共享的,这象征一个进程的内存空间是共享的,每个线程都可用这些共享内存

        多个工厂之间独立存在

        多进程:

                在同一个时间里,同一个计算机系统中如果允许两个或两个以上的进程处于运行状态,多进程带来的好处是明显的,比如你可以听歌的同时,打开编辑器敲代码,编辑器和听歌软件的进程之间丝毫不会相互干扰。

                例:浏览器每开一个Tab页,都是创建了一个进程

        多线程:

                程序中包含多个执行流,即在一个程序中可以运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务

                例:一个进程中可以有多个线程,比如:渲染线程、JS引擎线程、HTTP请求线程等等。当你发起一个请求时,其实就是创建了一个线程,当请求结束后,该线程可能就会被销毁。

2.浏览器内核

        简单来说浏览器内核是通过取得页面内容、整理信息(应用CSS)、计算和组合最终输出可视化的图像结果,通常也被称为渲染引擎。

        浏览器内核是多线程,在内核控制下各线程相互配合以保持同步,一个浏览器通常由以下常驻线程组成

                GUI渲染线程

                JS引擎线程

                定时触发器线程

                事件触发线程

                异步HTTP请求线程

        GUI渲染线程

                主要负责页面的渲染,解析HTM、CSS、构建DOM树、布局和绘制等。

                当界面需要重绘或者由于某种操作引发回流时,将执行该线程。

                该线程与JS引擎线程互斥,当执行JS引擎线程时,GUI渲染会被挂起,当任务队列空闲时,主线程才会去执行GUI渲染。

        JS引擎线程

                该线程主要负责处理JS脚本,执行代码

                主要负责执行准备好待执行的事件,即定时器计数结束,或者异步请求成功并正确返回时,将依次进入任务队列,等待JS引擎线程的执行

                该线程与GUI渲染线程互斥,当JS引擎线程执行JS脚本时间过长,将导致页面渲染的阻塞

        定时触发器线程

                负责执行异步定时器一类的函数的线程,如:setTimeout、setInterval

                主线程依次执行代码时,遇到定时器,会将定时器交给该线程处理,当计数完毕后,事件触发线程会将计数完毕后的事件加入到任务队列的尾部,等待JS引擎线程执行。

        事件触发线程

                主要负责将准备好的事件交给JS引擎线程执行

                        比如setTimeout定时器计数结束、AJAX等异步请求成功并触发回调函数,或者用户触发点击事件时,该线程会将整装待发的事件依次加入到任务队列的队尾,等待JS引擎线程的执行

        异步HTTP请求线程

                负责执行异步请求一类的函数的线程,如:Promise、fetch、AJAX等

                主线程依次执行代码时,遇到异步请求,会将函数交给该线程处理,当监听到状态码变更,如果有回调函数,事件触发线程会将回调函数加入到任务队列的尾部,等待JS引擎线程执行。

3.浏览器中的事件循环

        宏任务和微任务

                事件循环中的异步队列有两种:宏任务队列和微任务队列

                一个宏任务队列和一个微任务队列

                        先执行微任务队列,微任务队列执行完毕后执行宏任务(每执行一次宏任务都需要把微任务队列清空)

                常见的宏任务有:setTimeout、setInterval、requestAnimationFrame、script等

                常见的微任务有:new Promise().then(回调)、MutationObserver等

        事件循环流程

                1.一开始执行栈空,我们可以把执行栈认为是一个存储函数调用的栈结构,遵循先进后出的原则。微任务队列空,宏任务队列有且只有一个script脚本(整体代码)

                2.全局上下文(script标签)被推入执行栈,同步代码执行。在执行的过程中,会判断是同步任务还是异步任务,通过对一些接口的调用,可以产生新的宏任务与微任务,它们会分别被推入各自的任务队列里。同步代码执行完了,script脚本会被移出宏任务队列,这个过程本质上是队列的宏任务的执行和出队的过程

                3.上一步我们出队的是一个宏任务,这一步我们处理的是微任务。但需要注意的是:当一个宏任务执行完毕后,会执行所有的微任务,也就是将整个微任务队列清空。

                4.执行渲染操作,更新界面

                5.检查是否存在Web worker任务,如果有,则对其进行处理

                6.上述过程循环往复,查到两个队列都清空

                总结:

                        当某个宏任务执行完毕后,会查看是否有微任务队列。如果有,先执行微任务队列中的所有任务,如果没有,会读取宏任务队列中排在最前的任务,执行宏任务的过程中,遇到微任务,依次加入微任务队列。栈空后,再次读取微任务队列里的任务,依次类推

4.Node.js中的事件循环

        介绍

                Node,js中的事件循环和浏览器中的是完全不相同的东西。

                Node.js采用V8作为JS的解析引擎,而I/O处理方面使用了自己设计的libuv,libuv是一个基于事件驱动的跨平台抽象层,封装了不同操作系统一些底层特性,对外提供统一的API,事件循环机制也是它里面的实现

                多个宏任务队列,多个微任务队列

                Node.js的事件循环比浏览器端复杂很多。

                Node.js的运行机制:

                        V8引擎解析JS脚本

                        解析后的代码,调用NodeAPI

                        libuv库负责NodeAPI的执行。它将不同的任务分配给不同的线程,形成一个事件循环,以异步的方式将任务的执行结果返回给V8引擎

                        V8引擎再将结果返回给用户

        事件循环的6个阶段

                外部输入数据 -> 轮询阶段(poll)                               ->   检查阶段(check)           ->

                                          关闭事件回调阶段(close callback) ->   定时器检测阶段(timer)  ->

                                           I/O事件回调阶段(I/O callback)      ->   闲置阶段(idle、prepare)-> 

                                           轮询阶段(poll)   ->    .......

                poll阶段:                        获取新的I/O事件,适当的条件下Node.js将阻塞在这里

                check阶段:                     执行setImmediate()的回调

                close callbacks阶段:      执行socket的close事件回调

                timers阶段:                     这个阶段执行timer(setTimeout、setInterval)的回调

                I/O callbacks阶段:           处理一些上一轮循环中的少数未执行的I/O回调

                idle、prepare阶段:          仅Node.js内部使用

                        注意:上面六个阶段都不包括process.nextTick()

        timer阶段

                timers阶段会执行setTimeout和setInterval回调,并且是由poll阶段控制的。同样,在Node.js中定时器指定的时间也不是准确时间,只能是尽快执行

        poll阶段

                poll是一个至关重要的阶段,这一阶段中,系统会做两件事情:

                        回到timer阶段执行回调

                        执行I/O回调

                并且在进入该阶段时如果没有设定了timer的话,会发生以下两件事情:

                        如果poll队列不为空,会遍历回调队列并同步执行,直到队列为空或者达到系统限制

                        如果poll队列为空时,会有两件事发生:

                                如果有settimmediate回调需要执行,poll阶段会停止并且进入到check阶段执行回调

                                如果没有settimmediate回调需要执行,会等待回调被加入到队列中并立即执行回调,这里同样会有个超时时间设置防止一直等待下去

                当然设定了timer的话且poll队列为空,则会判断是否有timer超时,如果有的话会回到timer阶段执行回调

                假设poll被堵塞,那么即使timer已经到时间了也只能等着,这也是为什么上面说定时器指定的时间并不是准确的时间

        check阶段

                settimmediate()回调会被加入check队列中,check阶段的执行顺序在poll阶段之后

setTimeout和setImmediate区别

        二者非常相似,区别主要在于调用时机不同

                setTimeout:   设计在poll阶段为空闲时,且设定时间到达后执行,但它在timer阶段执行

                setImmediate:设计在poll阶段完成时执行,即check阶段

                执行时间

                        进入事件循环也是需要成本的,如果在准备时候花费了大于1ms的时间,那么在timer阶段就会直接执行setTimeout回调。如果准备时间小于1ms,那么就是setImmediate回调先执行

                        但当二者在异步I/O callback内部调用时,总是先执行setImmediate,再执行setTimeout

process.nextTick

        这个函数其实是独立于事件循环之外的,它有一个自己的队列。当每个阶段完成后,如果存在nextTick队列,就会清空队列中的所有回调函数,并且优先于其他microtask执行。

Promise.then

        Promise.then也是独立于事件循环之外的,有一个自己的队列,但是优先级要比process.nextTick要低,所以当微任务中同时存在process.nextTick和Promise.then时,会优先执行前者。

Node.js于浏览器的事件队列的差异

        浏览器环境下,就两个队列,一个宏任务队列,一个微任务队列。微任务的任务队列是每个宏任务执行完后执行

        Node.js中,微任务会在事件循环的各个阶段之间执行,也就是一个阶段执行完毕,就会去执行微任务队列的任务,每个任务队列的每个任务执行完毕之后,就会清空这个微任务队列

        总结:

                浏览器端:微任务在事件循环的宏任务执行完之后执行

                Node.js端:微任务在事件循环的各个阶段之间执行

 

                                                          

       


网站公告

今日签到

点亮在社区的每一天
去签到