Home >Web Front-end >JS Tutorial >A brief discussion on timers in Node.js_node.js
Implementation of timer in Node.js
As mentioned in the previous blog post, the timer in Node is not implemented by opening a new thread, but directly in the event loop. The following uses several JavaScript timer examples and Node related source code to analyze how the timer function is implemented in Node.
Features of the timer function in JavaScript
Whether it is Node or the browser, there are two timer functions, setTimeout and setInterval, and their working characteristics are basically the same, so the following only uses Node as an example for analysis.
We know that the timer in JavaScript is not different from the underlying scheduled interrupt of the computer. When an interrupt arrives, the currently executing code will be interrupted and transferred to the scheduled interrupt processing function. When the JavaScript timer expires, if there is no code being executed in the current execution thread, the corresponding callback function will be executed; if there is code currently being executed, the JavaScript engine will neither interrupt the current code to execute the callback, nor start The new thread executes the callback, but it is processed after the current code is executed.
console.time('A') setTimeout(function () { console.timeEnd('A'); }, 100); var i = 0; for (; i < 100000; i++) { }
Executing the above code, you can see that the final output time is not about 100ms, but a few seconds. This shows that the scheduled callback function is indeed not executed before the loop is completed, but is postponed until the end of the loop. In fact, during the execution of JavaScript code, all events cannot be processed, and new events must be processed until the current code is completed. This is why the browser becomes unresponsive when running time-consuming JavaScript code in it. In order to deal with this situation, we can use the Yielding Processes technique to divide the time-consuming code into small chunks (chunks), execute setTimeout once after each chunk is processed, and agree to process the next chunk after a short period of time. During this period During idle time, the browser/Node can process queued events.
Supplementary information
Advanced timers and Yielding Processes are discussed in more detail in Chapter 22 Advanced Techniques of JavaScript Advanced Programming, Third Edition.
Timer implementation in Node
libuv’s initialization of uv_loop_t type
The previous blog post mentioned that Node will call libuv's uv_run function to start default_loop_ptr for event scheduling. default_loop_ptr points to a variable default_loop_struct of type uv_loop_t. When Node starts, it will call uv_loop_init(&default_loop_struct) to initialize it. The excerpt of uv_loop_init function is as follows:
int uv_loop_init(uv_loop_t* loop) { ... loop->time = 0; uv_update_time(loop); ... }
You can see that the time field of the loop is first assigned a value of 0, and then the uv_update_time function is called, which assigns the latest counting time to loop.time.
After the initialization is completed, default_loop_struct.time has an initial value, and time-related operations will be compared with this value to determine whether to call the corresponding callback function.
libuv’s event scheduling core
As mentioned earlier, the uv_run function is the core part of the libuv library to implement event loop. The following is its flow chart:
Here is a brief description of the above logic related to the timer:
Update the time field of the current loop, which marks the "now" under the current loop concept;
Check whether the loop is alive, that is to say, check whether there are any tasks (handlers/requests) that need to be processed in the loop. If not, there is no need to loop;
Check the registered timers. If the time specified in a timer lags behind the current time, it means that the timer has expired, and its corresponding callback function is executed;
Perform an I/O polling (that is, block the thread and wait for the I/O event to occur). If no I/O is completed when the next timer expires, stop waiting and execute the callback of the next timer.
If an I/O event occurs, the corresponding callback will be executed; since another timer may have expired during the callback execution time, the timer must be checked again and the callback executed.
(Actually (4.) here is more complicated, not just a one-step operation. This description is just to not involve other details and focus on the implementation of the timer.)
Node will keep calling uv_run until the loop is no longer alive.
timer_wrap and timers in Node
There is a TimerWrap class in Node, which is registered as the timer_wrap module inside Node.
NODE_MODULE_CONTEXT_AWARE_BUILTIN(timer_wrap, node::TimerWrap::Initialize)
The TimerWrap class is basically a direct encapsulation of uv_timer_t, and NODE_MODULE_CONTEXT_AWARE_BUILTIN is a macro used by Node to register built-in modules.
After this step, JavaScript can get this module and operate it. The src/lib/timers.js file uses JavaScript to encapsulate the timer_wrap function and exports exports.setTimeout, exports.setInterval, exports.setImmediate and other functions.
Node startup and global initialization
The previous article mentioned that Node will load the execution environment LoadEnvironment(env) when it starts. A very important step in this function is to load src/node.js and execute it. src/node.js will load the specified module And initialize global and process. Of course, functions such as setTimeout will also be bound to the global object by src/node.js.
The above is the entire content of this article, I hope you all like it.