当前位置: 首页 > 后端技术 > Node.js

JavaScriptEventloop事件循环

时间:2023-04-03 17:40:18 Node.js

EventLoop本文以Node.js为例,讲解Node.js中EventLoop的实现。在原文中,JavaScript中的实现是类似的。什么是事件循环?单线程Node.js之所以能够实现非阻塞IO,是因为事件循环(EventLoop)。大多数系统内核现在都是多线程的,因此它们可以在后台执行多个操作。当这些操作完成后,内核会通知Node.js,并将这些操作的回调函数添加到事件轮询列表(pollqueue)中,Node.js会在合适的时候执行回调函数。事件循环概述当Node.js开始执行时,事件循环被初始化。执行过程中会有很多异步操作,比如:REPL、定时器(timers)、调用异步API(请求、事件监听),在主进程代码执行完之后,EventLoop就会开始运行。下图描述了EventLoop中的各个阶段┌────────────────────────────┐┌─>│定时器│这个阶段执行`setTimeout()`和`setInterval()`中的回调函数│└──────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┴──────────────┐││I/O回调│这个阶段执行几乎所有的I/O回调函数,除了`close`回调函数──────────────────┘│┌──────────────┴──────────────────────────┐┐│idle,prepare│这个阶段只有Node.js内部使用│└────────────┬────────────────────────────────────────────────────────────────────────────────┐│┌────────────┴──────────────┐│incoming:│││poll│<──────┤connections,│执行队列中的回调函数,检索新的回调函数│└────────────┬────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘│数据等────────────┐└──────────────────────┘││检查│`setImmediate()`将在这里调用│└──────────────┬──────────────┘│┌────────────┴────────────────┐└──┤关闭回调│`CLOSE`返回函数被调用。if:socket.on('close',...)──────────────────────────┘详细的EventLOOP'seachphaseoftimerssetTimeout()andsetInterval()必须指定运行时间。这个运行时间不是准确的运行时间,而是一个预期的时间。EventLoop会在定时器阶段执行超过时间限制定时器回调函数看时间,但是因为你不确定其他阶段甚至主进程的事件执行时间,所以定时器可能不会按时执行varasyncApi=function(callback){setTimeout(callback,90)}consttimeoutScheduled=Date.now();setTimeout(()=>{constdelay=Date.now()-timeoutScheduled;console.log(`${delay}mssetTimeoutexecuted`);//在140毫秒后执行},100);asyncApi(()=>{conststartCallback=Date.now();while(Date.now()-startCallback<50){//什么都不做}})I/O回调这个阶段主要执行一些系统带有Callback函数的操作,比如TCP错误,如果TCP尝试连接时出现ECONNREFUSED错误,一些*nix会向Node.js报告这个错误。而这个报错会先进入队列,然后在I/O回调阶段执行。poll阶段有两个主要功能:当定时器到达预期时间时,它也会执行回调函数。执行事件循环列表(轮询队列)中的函数当EventLoop进入轮询阶段且没有其他定时器时,则:如果事件循环列表不为空,则遍历同步执行队列中的函数。如果事件循环列表为空,判断是否有setImmediate()函数需要执行。如果轮询阶段结束,则直接进入检查阶段。如果没有,等待回调函数入队,立即执行。poll阶段结束后,check执行setImmediate()。这里会触发突然结束的close事件的回调函数。如果是socket.destroy(),那么这个阶段会触发close,也有可能被process.nextTick()触发。setImmediate(),setTimeout(),process.nextTick()这里我想说明下process.nextTick()运行在下一个事件循环之前。如果process.nextTick()和setImmediate()写在一起,那么就是process.nextTick()先执行。next比immediate快,官方也说这个函数的命名有问题,但是因为历史保留没办法解决。process.nextTick(()=>{console.log('nextTick');});setImmediate(()=>{console.log('setImmediate');});setTimeout(()=>{console.log('setTimeout');},0)//执行结果,nextTick,setTimeout,setImmediate//查看Node.js源码,setTimeout(fun,0)会转化为setTimeout(fun,1),所以在这个简单的在某些情况下,对于不同的设备,setImmediate可能比setTimeout更早执行。总结和理解事件循环,你就会知道JavaScript是如何无阻塞地运行的,以及它简洁的开发思想和事件驱动的风格。作者:肖木辰,github。