分享更有价值
被信任是一种快乐

Node.js事件循环是什么

文章页正文上

本文小编为大家详细介绍“Node.js事件循环是什么”,内容详细,步骤清晰,细节处理妥当,希望这篇“Node.js事件循环是什么”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。首先我们需要了解一下最基础的一些东西,比如这个事件循环,事件循环是指Node.js执行非阻塞I/O操作,尽管==JavaScript是单线程的==,但由于大多数==内核都是多线程==的,Node.js会尽可能将操作装载到系统内核。因此它们可以处理在后台执行的多个操作。当其中一个操作完成时,内核会告诉Node.js,以便Node.js可以将相应的回调添加到轮询队列中以最终执行。当Node.js启动时会初始化event loop, 每一个event loop都会包含按如下顺序六个循环阶段:1.timers阶段: 这个阶段执行setTimeout(callback)setInterval(callback)预定的 callback;2.I/O callbacks阶段: 此阶段执行某些系统操作的回调,例如TCP错误的类型。 例如,如果TCP套接字在尝试连接时收到 ECONNREFUSED,则某些* nix系统希望等待报告错误。 这将操作将等待在==I/O回调阶段==执行;3.idle, prepare阶段: 仅node内部使用;4.poll阶段: 获取新的I/O事件, 例如操作读取文件等等,适当的条件下node将阻塞在这里;5.check阶段: 执行setImmediate()设定的callbacks;6.close callbacks阶段: 比如socke免费云主机、域名t.on(‘close’, callback)的callback会在这个阶段执行;这个图是整个 Node.js 的运行原理,从左到右,从上到下,Node.js 被分为了四层,分别是应用层V8引擎层Node API层LIBUV层。应用层: 即 JavaScript 交互层,常见的就是 Node.js 的模块,比如 http,fsV8引擎层: 即利用 V8 引擎来解析JavaScript 语法,进而和下层 API 交互NodeAPI层: 为上层模块提供系统调用,一般是由 C 语言来实现,和操作系统进行交互 。LIBUV层: 是跨平台的底层封装,实现了 事件循环、文件操作等,是 Node.js 实现异步的核心 。timers阶段一个timer指定一个下限时间而不是准确时间,在达到这个下限时间后执行回调。在指定时间过后,timers会尽可能早地执行回调,但系统调度或者其它回调的执行可能会延迟它们。注意:技术上来说,poll 阶段控制 timers 什么时候执行。注意:这个下限时间有个范围:[1, 2147483647],如果设定的时间不在这个范围,将被设置为1。I/O callbacks阶段这个阶段执行一些系统操作的回调。比如TCP错误,如一个TCP socket在想要连接时收到ECONNREFUSED, 类unix系统会等待以报告错误,这就会放到 I/O callbacks 阶段的队列执行. 名字会让人误解为执行I/O回调处理程序, 实际上I/O回调会由poll阶段处理.poll阶段poll 阶段有两个主要功能:(1)执行下限时间已经达到的timers的回调,(2)然后处理 poll 队列里的事件。 当event loop进入 poll 阶段,并且 没有设定的 timers(there are no timers scheduled),会发生下面两件事之一:如果 poll 队列不空,event loop会遍历队列并同步执行回调,直到队列清空或执行的回调数到达系统上限;如果 poll 队列为空,则发生以下两件事之一:如果代码已经被setImmediate()设定了回调, event loop将结束 poll 阶段进入 check 阶段来执行 check 队列(里面的回调 callback)。如果代码没有被setImmediate()设定回调,event loop将阻塞在该阶段等待回调被加入 poll 队列,并立即执行。但是,当event loop进入 poll 阶段,并且 有设定的timers,一旦 poll 队列为空(poll 阶段空闲状态): event loop将检查timers,如果有1个或多个timers的下限时间已经到达,event loop将绕回 timers 阶段,并执行 timer 队列。check阶段这个阶段允许在 poll 阶段结束后立即执行回调。如果 poll 阶段空闲,并且有被setImmediate()设定的回调,event loop会转到 check 阶段而不是继续等待。setImmediate() 实际上是一个特殊的timer,跑在event loop中一个独立的阶段。它使用libuv的API 来设定在 poll 阶段结束后立即执行回调。通常上来讲,随着代码执行,event loop终将进入 poll 阶段,在这个阶段等待 incoming connection, request 等等。但是,只要有被setImmediate()设定了回调,一旦 poll 阶段空闲,那么程序将结束 poll 阶段并进入 check 阶段,而不是继续等待 poll 事件们 (poll events)。close callbacks阶段如果一个 socket 或 handle 被突然关掉(比如 socket.destroy()),close事件将在这个阶段被触发,否则将通过process.nextTick()触发这里呢,我们通过伪代码来说明一下,这个流程:我们来看一个简单的EventLoop的例子:这里呢,为了让这个setTimeout优先于fs.readFile回调, 执行了process.nextTick, 表示在进入timers阶段前, 等待20ms后执行文件读取.process.nextTick不属于事件循环的任何一个阶段,它属于该阶段与下阶段之间的过渡, 即本阶段执行结束, 进入下一个阶段前, 所要执行的回调。有给人一种插队的感觉.setImmediate的回调处于check阶段, 当poll阶段的队列为空, 且check阶段的事件队列存在的时候,切换到check阶段执行,参考nodejs进阶视频讲解:进入学习由于nextTick具有插队的机制,nextTick的递归会让事件循环机制无法进入下一个阶段. 导致I/O处理完成或者定时任务超时后仍然无法执行, 导致了其它事件处理程序处于饥饿状态. 为了防止递归产生的问题, Node.js 提供了一个 process.maxTickDepth (默认 1000)。此时永远无法跳到timer阶段去执行setTimeout里面的回调方法, 因为在进入timers阶段前有不断的nextTick插入执行. 除非执行了1000次到了执行上限,所以上面这个案例会不断地打印出nextTick字符串如果在一个I/O周期内进行调度,setImmediate() 将始终在任何定时器(setTimeout、setInterval)之前执行.setImmediate()被设计在 poll 阶段结束后立即执行回调;setTimeout()被设计在指定下限时间到达后执行回调;无 I/O 处理情况下:执行结果:从结果,我们可以发现,这里打印输出出来的结果,并没有什么固定的先后顺序,偏向于随机,为什么会发生这样的情况呢?答:首先进入的是timers阶段,如果我们的机器性能一般,那么进入timers阶段,1ms已经过去了 ==(setTimeout(fn, 0)等价于setTimeout(fn, 1))==,那么setTimeout的回调会首先执行。如果没有到1ms,那么在timers阶段的时候,下限时间没到,setTimeout回调不执行,事件循环来到了poll阶段,这个时候队列为空,于是往下继续,先执行了setImmediate()的回调函数,之后在下一个事件循环再执行setTimemout的回调函数。问题总结:而我们在==执行启动代码==的时候,进入timers的时间延迟其实是==随机的==,并不是确定的,所以会出现两个函数执行顺序随机的情况。那我们再来看一段代码:打印结果如下:这里,为啥和上面的随机timer不一致呢,我们来分析下原因:原因如下:fs.readFile的回调是在poll阶段执行的,当其回调执行完毕之后,poll队列为空,而setTimeout入了timers的队列,此时有代码setImmediate(),于是事件循环先进入check阶段执行回调,之后在下一个事件循环再在timers阶段中执行回调。当然,下面的小案例同理:以上的代码在timers阶段执行外部的setTimeout回调后,内层的setTimeoutsetImmediate入队,之后事件循环继续往后面的阶段走,走到poll阶段的时候发现队列为空,此时有代码有setImmedate(),所以直接进入check阶段执行响应回调(==注意这里没有去检测timers队列中是否有成员到达下限事件,因为setImmediate()优先==)。之后在第二个事件循环的timers阶段中再去执行相应的回调。综上所演示,我们可以总结如下:如果两者都在主模块中调用,那么执行先后取决于进程性能,也就是你的电脑好撇,当然也就是随机。如果两者都不在主模块调用(被一个异步操作包裹),那么**setImmediate的回调永远先执行**。概念:对于这两个,我们可以把它们理解成一个微任务。也就是说,它其实不属于事件循环的一部分。 那么他们是在什么时候执行呢? 不管在什么地方调用,他们都会在其所处的事件循环最后,事件循环进入下一个循环的阶段前执行。控制台打印如下:最总结:timers阶段执行外层setTimeout的回调,遇到同步代码先执行,也就有timeout0sync的输出。遇到process.nextTickPromise后入微任务队列,依次nextTick1nextTick3nextTick2resolved入队后出队输出。之后,在下一个事件循环的timers阶段,执行setTimeout回调输出timeout2以及微任务Promise里面的setTimeout,输出timeout resolved。(这里要说明的是 微任务nextTick优先级要比Promise要高)代码片段1:解析:事件循环check阶段执行回调函数输出setImmediate,之后输出nextTick。嵌套的setImmediate在下一个事件循环的check阶段执行回调输出嵌套的setImmediate。代码片段2:打印结果为:读到这里,这篇“Node.js事件循环是什么”文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注云技术行业资讯频道。

相关推荐: vue中如何使用socket

这篇“vue中如何使用socket”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“vue中如何使用socket”文章吧。 WebSocket是一种…

文章页内容下
赞(0) 打赏
版权声明:本站采用知识共享、学习交流,不允许用于商业用途;文章由发布者自行承担一切责任,与本站无关。
文章页正文下
文章页评论上

云服务器、web空间可免费试用

宝塔面板主机、支持php,mysql等,SSL部署;安全高速企业专供99.999%稳定,另有高防主机、不限制内容等类型,具体可咨询QQ:360163164,Tel同微信:18905205712

主机选购导航云服务器试用

登录

找回密码

注册