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

Node的事件机制

时间:2023-04-03 11:10:28 Node.js

什么是事件循环?js虽然是单线程的,但是事件循环机制允许node通过在适当的时候将操作传递给系统内核来进行非阻塞的io操作。当操作完成后,内核通知node.js将添加适当的回调函数轮询队列并最终被执行。当Node.js启动时,它会初始化事件循环并处理提供的脚本。脚本可能会调用异步API、调度定时器或调用process.nextTick(),然后处理事件循环。下图是事件循环运行时序图的简化概览┌──────────────────────────────┐┌─>│定时器││└──────────────────────────────────────┘│┌────────────┴──────────────────────────────────────────────────────────────────────────┐││I/O回调││└────────────┬────────────────┘│┌────────────────────────────────────────────────────────────────┐││空闲,准备││└──────────────┬──────────────┘┘──────────────────────────────────┐│┌──────────────────────────────────┐│传入:││POLL│<──────┤连接,│└────────────────────────┬──────────────────┬────────────────────────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────新闻────────────┘│数据等││┌──────────────────────────────────────┐└────────────────┘││检查││└────────────┬────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┴──────────────┐└──┤收盘回调│└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘一个盒子就是一个阶段,每个阶段都有一个先进先出的回调函数队列。当事件循环进入一个阶段时,会执行该阶段的所有操作,然后执行回调函数,直到队列耗尽,或者回调函数执行次数达到最大值,然后进入下一个阶段,因为有任何operation可能会调度更多的操作,而poll阶段的新事件会被内核排队,所以在处理polling事件时,pollevent可能会排队。结果:长时间运行的回调函数允许轮询阶段运行时间超过计时器阈值的事件。阶段概述定时器:执行由setTimeout()和setInterval()调度的回调函数I/O回调:执行所有回调函数,除了关闭回调(由定时器调度,setImmediate())空闲,准备:在内部使用轮询:获取新的io事件,在适当的时候,节点会在这里阻塞检查:setImmediate()回调函数会在这里调用关闭回调:例如socket.on('close',...)每次运行事件循环,节点检查是否有等待任何异步io或定时器,如果没有,关闭PhasesinDetail(每个阶段的详细描述)。timers定时器指定一个阈值(threshold)后,回调函数就会执行,但阈值并不是执行回调函数的准确时间(只是最短时间)。定时器回调函数将在准备好执行时立即执行。但是,操作系统的调度程序或其他回调函数可能会延迟其执行。轮询阶段控制计时器何时执行varfs=require('fs');functionsomeAsyncOperation(callback){//假设这需要95毫秒才能完成fs.readFile('/path/to/file',callback);}vartimeoutScheduled=Date.now();setTimeout(function(){vardelay=Date.now()-timeoutScheduled;console.log(delay+"mshavepassedsinceIwasscheduled");},100);//执行需要95毫秒才能完成的someAsyncOperation(function(){varstartCallback=Date.now();//执行需要10毫秒的操作...while(Date.now()-startCallback<10){;//没做什么}});一开始是定时器调度,里面的回调函数执行日志。然后事件循环进入轮询阶段。此时队列为空(因为fs.readFile()没有完成),所以会等到最早的定时器阈值(100)到达时间,等待95ms(还没有,毕竟是设置为100),此时fs.readFile()完成,所以它的回调函数返回polled队列并执行(执行10s),当回调函数完成后,队列再次为空,所以,eventloop会看到定时器阈值(100)已经到了,然后回到timers阶段执行定时器的回调函数,即打印出105秒。为了防止轮询阶段排他性地耗尽事件循环,libuv还有一个最大值(基于系统),在超过最大值之前停止轮询更多的事件。I/O回调执行系统操作的回调函数(例如tcp错误类型)。当tcpsockets尝试连接并接收ECONNREFUSED时,类Unix系统会想要报错,在这个阶段会排队等待执行。pollpoll阶段有两个函数,当时间到时,为定时器执行脚本,然后处理poll队列中的事件。当eventloop进入poll阶段,没有timer被调度时,会发生poll不为空的情况,通过回调函数队列迭代执行poll栈。为空如果脚本已经被setImmediate()调度,事件循环会终止poll阶段,进入check阶段执行那些调度的脚本,等待回调函数加入队列,然后立即执行.一旦poll为空,eventloop会回头检查timers是否有阈值,如果有则回绕到timers阶段,然后执行timers的回调函数检查特殊的timerclose回调setImmediate和setTimeout()在poll完成后执行在最小事件之后执行执行顺序:取决于如果调用的上下文在主模块中,则事件将受限于进程的性能(受其他应用程序的影响)不在一个I/O周期内:在一个I/O周期内不确定I/O周期:immediate总是在前(更好)//timeout_vs_immediate.jssetTimeout(functiontimeout(){console.log('timeout');},0);setImmediate(functionimmediate(){console.log('immediate');});//timeout_vs_immediate.jsvarfs=require('fs')fs.readFile(__filename,()=>{setTimeout(()=>{console.log('timeout')},0)setImmediate(()=>{console.log('immediate')})})Node.js事件循环,定时器参考: