In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-03-28 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/03 Report--
In this issue, the editor will bring you about how to understand the event cycle in Nodejs. The article is rich in content and analyzes and narrates it from a professional point of view. I hope you can get something after reading this article.
Node event loop
Libuv, the underlying language used by Node, is a C++ language. It is used to operate the underlying operating system and encapsulates the interface of the operating system. Node's event cycle is also written in libuv, so there is a difference between the Node life cycle and the browser's.
Because Node deals with the operating system, the event loop is complex and has its own unique API.
Event loops vary slightly from operating system to operating system. This will involve the knowledge of the operating system, not for the time being. This time only introduces the operation flow of Node in the main thread of JS. Other threads in Node are not expanding for the time being.
Event cycle diagram
The picture we talked about doesn't matter. The following picture is clear, and the event cycle will be learned.
Event cycle diagram
Event Loop Diagram-structure
In order for everyone to have an overall vision first, post a directory structure map at the front:
Catalogue
Next, let's talk about it in detail.
Main thread
Main thread
In the image above, the meaning of several color blocks:
Main: start the entry file and run the main function
Event loop: check if you want to enter the event loop
Check to see if there is anything to be done in other threads
Check whether other tasks are still in progress (such as timers, file reads, etc.)
In the above situation, enter the event loop and run other tasks
The process of the event cycle: follow the process from timers to close callbacks, walk around. Go to event loop to see if it's over, and walk around again if it's not over.
Over: everything is over. It's over.
Event loop
Event loop
The gray circle in the figure has something to do with the operating system and is not the focus of this chapter. Focus on yellow, orange circles and orange boxes in the middle.
We call each cycle of events "a cycle", also known as "a poll", also known as "a Tick".
A cycle goes through six stages:
Timers: timer (callback functions of setTimeout, setInterval, etc. Are stored in it)
Pending callback
Idle prepare
Poll: polling queue (callbacks except timers and check are stored here)
Check: check phase (callbacks using setImmediate will go directly to this queue)
Close callbacks
This time we only focus on the three key points of the above standard red.
working principle
An event queue is maintained for each phase. Think of each circle as a queue of events.
This is different from the browser, which has up to two queues (macro queue, micro queue). But there are six queues in node.
After arriving in a queue, check to see if there are any tasks in the queue (that is, to see if there is a callback function) to execute. If so, it is executed in turn until all execution is completed and the queue is cleared.
If there is no task, go to the next queue to check. Until all the queues are checked, it counts as a poll.
After the execution of timers, pending callback, idle prepare, etc., they arrive at the poll queue.
How timers queues work
Timers is not really a queue, what he stores inside is a timer.
Each time this queue is reached, all timers within the timer thread are checked, and multiple timers within the timer thread are sorted in chronological order.
Check process: calculate each timer separately in order to calculate whether the time from the start of the timer to the current time satisfies the interval parameter setting of the timer (for example, 1000ms, calculate whether there is 1m from the start of the timer to the present). When a timer is checked, its callback function is executed.
How poll queues operate
If there is a callback function in poll that needs to be executed, execute the callback in turn until the queue is cleared.
If there is no callback function to execute in poll, it is already an empty queue. Will wait here, waiting for callbacks in other queues.
If a callback occurs in other queues, it goes down from poll to over, ending that phase and moving on to the next phase.
If none of the other queues have a callback, wait in the poll queue until there is a callback in any queue before working. (it's the way a little slacker does things.)
Example combing event flow setTimeout (() = > {console.log ('object');}, 5000) console.log (' node'); event flow combing of the above code
Enter the main thread, execute setTimeout (), and the callback function is put into the asynchronous queue timers queue as an asynchronous task, not executed for the time being.
Continue down, execute the console after the timer, and print "node".
Determine if there is an event loop. Yes, take a round-trip poll: from timers-pending callback-idle prepare...
Stop the loop in the poll queue and wait.
Since there are no tasks in the timers queue in less than 5 seconds, it is stuck in the poll queue all the time, while polling to see if there are any tasks in other queues.
When the callback of setTimeout arrives in 5 seconds, the callback of setTimeout is plugged into the timers. Routine polling checks that there is a task in the timers queue, then go down and go through check and close callbacks to reach timers. Empty the timers queue.
Continue polling to poll and wait, asking if you still need event loop. If not, you will reach the end of over.
To understand this problem, see the following code and process parsing: setTimeout (function T1 () {console.log ('setTimeout');}, 5000) console.log (' node life cycle'); const http = require ('http') const server = http.createServer (function H2 () {console.log (' request callback');}); server.listen (8080)
The code analysis is as follows:
As usual, execute the main thread first, print the "node life cycle", introduce http and then create the http service.
Then event loop checks to see if there are asynchronous tasks and checks to find timer tasks and request tasks. So go into the event loop.
If there are no tasks in all six queues, wait in the poll queue. As shown below:
After five seconds, when there is a task in timers, the process releases down from poll, passes through the check and close callbacks queues, and arrives at event loop.
Event loop checks if there are asynchronous tasks, and checks to find timer tasks and request tasks. So enter the event loop again.
When you arrive in the timers queue and find that there is a callback function task, you will execute the callback in turn, emptying the timers queue (of course, there is only a callback after 5 seconds of arrival, so you can directly execute it), and print out "setTimeout". The figure below is as follows
After emptying the timers queue, polling continues down to the poll queue, and because the poll queue is now empty, wait here.
Later, assuming that the user request comes in, the H2 callback function is placed in the poll queue. So there is a callback function in poll that needs to be executed, which in turn executes the callback until the poll queue is cleared.
The poll queue is empty, while the poll queue is empty, continue to wait.
Since the Node thread has been holding in the poll queue for a long time, when there is no task coming, it will automatically disconnect the wait (unconfident performance), execute the polling process down, and arrive at event loop after check and close callbacks.
When you get to event loop, check to see if there are asynchronous tasks, and check to find that there are requested tasks. (at this point, the timer task has been executed, so it is gone.), continue to enter the event loop again.
Arrive at the poll queue and holding again.
Wait a long time for no task to come, automatically disconnect to even loop (add a little bit of no-task loop)
Go back to the poll queue again and hang
An infinite cycle...
Comb the event cycle flowchart:
Note: the phrase "whether there is a task" in the following figure indicates "whether there is a task for this queue".
Event loop process carding
Then verify the process with a typical example: const startTime = new Date (); setTimeout (function F1 () {console.log ('setTimeout', new Date (), new Date ()-startTime);}, 200) console.log (' node Life cycle', startTime); const fs = require ('fs') fs.readFile ('. / poll.js', 'utf-8', function fsFunc (err, data) {const fsTime = new Date () console.log (' fs', fsTime) While (new Date ()-fsTime
< 300) { } console.log('结束死循环', new Date());}); 连续运行三遍,打印结果如下: 执行流程解析: 执行全局上下文,打印「node 生命周期 + 时间」 询问是否有event loop 有,进入timers队列,检查没有计时器(cpu处理速度可以,这时还没到200ms) 轮询进入到poll,读文件还没读完(比如此时才用了20ms),因此poll队列是空的,也没有任务回调 在poll队列等待……不断轮询看有没有回调 文件读完,poll队列有了fsFunc回调函数,并且被执行,输出「fs + 时间」 在while死循环那里卡300毫秒, 死循环卡到200ms的时候,f1回调进入timers队列。但此时poll队列很忙,占用了线程,不会向下执行。 直到300ms后poll队列清空,输出「结束死循环 + 时间」 event loop赶紧向下走 再来一轮到timers,执行timers队列里的f1回调。于是看到「setTimeout + 时间」 timers队列清空,回到poll队列,没有任务,等待一会。 等待时间够长后,向下回到event loop。 event loop检查没有其他异步任务了,结束线程,整个程序over退出。 check 阶段 检查阶段(使用 setImmediate 的回调会直接进入这个队列) check队列的实际工作原理 真正的队列,里边扔的就是待执行的回调函数的集合。类似[fn,fn]这种形式的。 每次到达check这个队列后,立即按顺序执行回调函数即可【类似于[fn1,fn2].forEach((fn)=>Feeling of fn ()]
So setImmediate is not a timer concept.
If you go to the interview, when it comes to Node, you may encounter the following question: who is faster, setImmediate or setTimeout (0).
Comparison between setImmediate () and setTimeout (0)
The callback of setImmediate is asynchronous and is consistent with the nature of setTimeout callback.
The setImmediate callback is in the check queue, and the setTimeout callback is in the timers queue (conceptually, it's actually in the timer thread, it's just that setTimeout makes a check call on the timers queue. Take a closer look at how timers works.
After the setImmediate function is called, the callback function immediately push to the check queue and is executed the next time eventloop. After the setTimeout function is called, the timer thread adds a timer task. The next eventloop will check to determine whether the timer task arrives in the timers phase, and then execute the callback function.
To sum up, setImmediate is faster than setTimeout (0) because setTimeout also needs to open timer threads and increase computational overhead.
The effects of the two are similar. But the execution order is out of order.
Observe the following code:
SetTimeout (() = > {console.log ('setTimeout');}, 0); setImmediate (() = > {console.log (' setImmediate');})
Run it repeatedly, and the execution results are as follows:
The order is uncertain.
You can see that it has been run many times, and the order in which the two sentences of console.log are printed is out of order.
This is because the minimum number of intervals for setTimeout is 1, although the following code is filled with 0. But the actual computer executes when 1ms calculates. (note here that it is distinguished from the browser timer. In the browser, the minimum interval for setInterval is 10ms, and if it is less than 10ms, it will be set to 10; when the device is powered, the minimum interval is 16.6ms. )
The above code, when the main thread is running, the setTimeout function is called, and the timer thread adds a timer task. After the setImmediate function is called, its callback function immediately push to the check queue. The execution of the main thread is complete.
When eventloop judges, it finds that timers and check queues have content, and enters asynchronous polling:
The first case: wait until this period of time in timers, there may be no time for 1ms, and the condition of timer task interval is not valid, so there is no callback function in timers. Continue down to the check queue, where the callback function of setImmediate has been waiting for a long time to be executed directly. The next time eventloop arrives at the timers queue, the timer is already ripe before the callback task of setTimeout will be executed. So the order is "setImmediate-> setTimeout".
The second case: but it is also possible to exceed the 1ms when it reaches the timers stage. So the calculation timer condition is established, and the callback function of setTimeout is executed directly. Eventloop then goes down to the check queue to perform the callback of setImmediate. The final order is "setTimeout-> setImmediate".
Therefore, when only comparing the two functions, the final result of the execution order of the two functions depends on the running environment and speed of the current computer.
Code for comparison of the time gap between the two-setTimeout test:-let I = 0 console.time ('setTimeout'); function test () {if (I)
< 1000) { setTimeout(test, 0) i++ } else { console.timeEnd('setTimeout'); }}test();------------------setImmediate测试:-------------------let i = 0;console.time('setImmediate');function test() { if (i < 1000) { setImmediate(test) i++ } else { console.timeEnd('setImmediate'); }}test(); 运行观察时间差距: setTimeout与setImmediate时间差距 可见setTimeout远比setImmediate耗时多得多 这是因为setTimeout不仅有主代码执行的时间消耗。还有在timers队列里,对于计时器线程中各个定时任务的计算时间。 结合poll队列的面试题(考察timers、poll和check的执行顺序) 如果你看懂了上边的事件循环图,下边这道题难不倒你! // 说说下边代码的执行顺序,先打印哪个?const fs = require('fs')fs.readFile('./poll.js', () =>{setTimeout (() = > console.log ('setTimeout'), 0) setImmediate (() = > console.log (' setImmediate'))})
The above code logic, no matter how many times it is executed, must execute setImmediate first.
Execute setImmediate first
Because the callback of each function of fs is placed in the poll queue. When the program holding is in the poll queue, the callback is executed immediately.
After the functions of setTimeout and setImmediate are executed in the callback, the callback is added to the check queue immediately.
After the callback is completed, the polling checks the contents of other queues, and the holding of the poll queue is executed downwards at the end of the program.
Check is the next step in the poll phase. So in the downward process, first execute the callback within the check phase, that is, print the setImmediate first.
When the next loop arrives in the timers queue and checks that the setTimeout timer meets the criteria, the timer callback is executed.
NextTick and Promise
After talking about macro tasks, let's move on to micro tasks.
Both are "microqueues" that perform asynchronous microtasks.
The two are not part of the event loop, and the program does not open additional threads to handle related tasks. (understand: send a network request in promise, which is the network thread of the network request, which has nothing to do with the micro-task of Promise.)
The purpose of microqueues is to give priority to some tasks "immediately" and "immediately".
NextTick has a higher level of nextTick than Promise.
NextTick expression process.nextTick (() = > {}) Promise expression Promise.resolve (). Then (() = > {}) how does it participate in the event cycle?
In the event loop, nextTick and promise are cleared sequentially before each callback is executed.
/ / first consider the execution order of the following code setImmediate (() = > {console.log ('setImmediate');}); process.nextTick (() = > {console.log (' nextTick 1'); process.nextTick () = > {console.log ('nextTick 2');}) console.log (' global'); Promise.resolve () .then () = > {console.log ('promise 1') Process.nextTick (() = > {console.log ('nextTick in promise');})})
Final order:
Global
NextTick 1
NextTick 2
Promise 1
NextTick in promise
SetImmediate
Two questions:
Based on the above, there are two problems to be considered and solved:
Check nextTick and promise every time you walk through an asynchronous macro task queue. Or is it checked every time you finish executing a callback function in the macro task queue?
If a callback of nextTick or Promise is inserted during the holding phase of poll, will the holding of the poll queue be stopped immediately to perform the callback?
For the above two questions, see what the following code says
SetTimeout (() = > {console.log ('setTimeout 100'); setTimeout () = > {console.log (' setTimeout 100'); process.nextTick (() = > {console.log ('nextTick in setTimeout 100');})}, 0) setImmediate () = > {console.log (' setImmediate in setTimeout 100'); process.nextTick (() = > {console.log ('nextTick in setImmediate in setTimeout 100');})}) Process.nextTick (() = > {console.log ('nextTick in setTimeout100');}) Promise.resolve (). Then (() = > {console.log (' promise in setTimeout100');})}, 100) const fs = require ('fs') fs.readFile ('. / 1.poll.jsgiving, () = > {console.log ('poll 1'); process.nextTick () = > {console.log (' nextTick in poll =') })}) setTimeout (() = > {console.log ('setTimeout 0'); process.nextTick (() = > {console.log (' nextTick in setTimeout');})}, 0) setTimeout () = > {console.log ('setTimeout1'); Promise.resolve (). Then () = > {console.log (' promise in setTimeout1');}) process.nextTick (() = > {console.log ('nextTick in setTimeout1')) }), 1) setImmediate () = > {console.log ('setImmediate'); process.nextTick () = > {console.log (' nextTick in setImmediate');})}); process.nextTick () = > {console.log ('nextTick 1'); process.nextTick () = > {console.log (' nextTick 2');}) console.log ('global -') Promise.resolve (). Then (() = > {console.log ('promise 1'); process.nextTick (() = > {console.log ('nextTick in promise');})}) / * * the sequence of execution is as follows: global-nextTick 1nextTick 2promise 1nextTick in promisesetTimeout 0 / / explains problem 1. The order of nextTick and promise,setTimeout and setImmediate without the above is not necessarily, after having it, it must be 0 first. / / it can be seen that before executing a queue, check and execute the nextTick and setTimeout 100nextTick in setTimeout100promise in setTimeout100setImmediate in setTimeout 100nextTick in setImmediate in setTimeout 100setTimeout micro queues nextTick in setTimeoutsetTimeout 1nextTick in setTimeout1promise in setTimeout1setImmediatenextTick in setImmediatepoll 1nextTick in poll = setTimeout 100nextTick in setTimeout100promise in setTimeout100setImmediate in setTimeout 100nextTick in setImmediate in setTimeout 100setTimeout 100-0nextTick in setTimeout100-0 * /
The above code is executed multiple times, the order remains the same, the order of setTimeout and setImmediate remains the same.
The order of execution and the specific reasons are as follows:
Global: the main thread synchronizes tasks, so there is nothing wrong with taking the lead in executing them.
NextTick 1: clear asynchronous micro tasks before executing asynchronous macro tasks. NextTick priority is high, so go ahead.
NextTick 2: after executing the above code, another NextTick micro task will be executed first immediately.
Promise 1: clear asynchronous micro tasks before executing asynchronous macro tasks. Promise has a low priority, so execute immediately after nextTick is finished.
NextTick in promise: when you encounter a nextTick micro task in the process of emptying the Promise queue, execute and empty it immediately.
SetTimeout 0: explain the first question. There is no nextTick and promise above, only setTimeout and setImmediate, their execution order is not necessarily. When you have it, it must start with zero. It can be seen that the nextTick and evaluate microqueues are checked and executed sequentially before a macro queue is executed. When the microqueue is fully executed, the time for setTimeout (0) is ripe, and it will be executed.
NextTick in setTimeout: after executing the above code, another NextTick micro task will be executed first immediately. I'm not sure whether the micro task in this callback function is executed immediately after the synchronous task, or put it in the micro task queue and empty them before the next macro task is executed. But the order seems to be the same as executing them immediately. But I prefer the latter: wait in the micro-task queue and empty them before the next macro task is executed. ]
SetTimeout 1: both setTimeout callbacks have been queued and executed because the setTimeout timer for judging two zeros and 1s in timers has ended because it takes time to execute the microtask.
NextTick in setTimeout1: after executing the above code, another NextTick micro task will be executed first immediately [probably before the next macro task]
Promise in setTimeout1: after executing the above code, another promise micro task is immediately followed by execution [probably before the next macro task]
Before the time for setImmediate:poll queue callback, go down to check queue first, clear the queue, and immediately execute setImmediate callback.
NextTick in setImmediate: after executing the above code, another NextTick micro task will be executed first immediately [probably before the next macro task]
The poll 1:poll queue is actually mature, the callback is triggered, and the synchronous task is executed.
NextTick in poll: after executing the above code, another NextTick micro task will be executed first immediately [probably before the next macro task]
SetTimeout 100: timer task arrival time, execute callback. NextTick and Promise are pushed into the micro task in the callback, and setImmediate callback is pushed into the check of the macro task. The timer thread is also started, adding the possibility of the next callback to the timers.
NextTick in setTimeout100: the macro task goes down and takes the lead in executing the micro task added in the timer callback-nextTick [it can be determined here that it is the process of emptying the micro task before the next macro task]
Promise in setTimeout100: then execute the new micro task in timer callback-Promise [order of emptying nextTick and emptying Promise after emptying]
SetImmediate in setTimeout 100: the reason why setImmediate is executed before setTimeout (0) this time is that the process moves backward from timers to check queue, and there is already a callback from setImmediate, so it is executed immediately.
NextTick in setImmediate in setTimeout 100: after executing the above code, there is another NextTick micro task, and the micro task is cleared before the next macro task.
SetTimeout 100-0: the poll returns to timers again, performing a callback of 100-0.
NextTick in setTimeout 100-0: after executing the above code, there is another NextTick micro task, and the micro task is emptied first before the next macro task.
Extension: why nextTick and Promise when you have setImmediate?
At the beginning of the design, setImmediate acted as a micro-queue (although he wasn't). The designer wants to execute setImmediate as soon as the poll is executed (as it does now, of course). So the name is Immediate, which means immediately. But then the problem is that there may be N tasks in the poll that are executed continuously, and it is impossible to execute setImmediate during execution. Because the poll queue is non-stop, the process does not go down.
Hence the emergence of nextTick, the real concept of microqueuing. But at this point, the name of immediate is occupied, so it is called nextTick (next moment). During the event loop, check to see if any queue has been emptied before executing it. The second is Promise.
Interview questions
Finally, the interview questions to test the learning achievements came.
Async function async1 () {console.log ('async start'); await async2 (); console.log (' async end');} async function async2 () {console.log ('async2');} console.log (' script start'); setTimeout () = > {console.log ('setTimeout 0');}, 0) setTimeout () = > {console.log (' setTimeout 3');}, 3) setImmediate () = > {console.log ('setImmediate') }) process.nextTick () = > {console.log ('nextTick');}) async1 (); new Promise ((res) = > {console.log (' promise1'); res (); console.log ('promise2');}). Then () = > {console.log (' promise 3');}); console.log ('script end') / / the answer is as follows / /-/ * * the running order of the last three is the time to verify the computing speed of your computer. / / the answer is as follows / script startasync startasync2promise1promise2script endnextTickasync endpromise 3 script startasync startasync2promise1promise2script endnextTickasync endpromise. The speed is the best (it takes less than 0ms to execute the synchronization code + micro-task + timer operation above): setImmediatesetTimeout 0setTimeout 3 has medium speed (it takes more than 0~3ms to execute the synchronization code + micro-task + timer operation above): setTimeout 0setImmediatesetTimeout 3 has a poor speed (it takes more than 3ms to execute the synchronization code + micro-task + timer operation above): setTimeout 0setTimeout 3setImmediate*/ mind map-the core phase of the Node life cycle
The above is how to understand the event cycle in Nodejs shared by the editor. If you happen to have similar doubts, you might as well refer to the above analysis to understand. If you want to know more about it, you are welcome to follow the industry information channel.
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.