Overview of Node.js event-driven implementation
Although "events" are not (and are not necessary) clearly defined in the ECMAScript standard, events serve as an extremely important mechanism in browsers, giving JavaScript the ability to respond to user operations and DOM changes; in Node. In js, the asynchronous event-driven model is the basis of its high concurrency capability.
Learning JavaScript also requires understanding its running platform. In order to better understand the event model of JavaScript, I plan to start with Node and browser engine source code, analyze its underlying implementation, and organize my analysis into a series of blog posts; On the one hand, it is a note, and on the other hand, I hope to communicate with everyone. If there are any omissions and biases in the analysis and understanding, I hope you will correct me.
A brief description of the event-driven model
There are already many good articles explaining the JavaScript event model itself. It can be said that this is already a poorly discussed topic. Here I will only briefly write about it and provide links to some good articles.
How the program responds to events
Our program responds to external events in the following two ways:
Interruption
The operating system handles keyboard and other hardware input through interrupts. The advantage of this method is that even without multi-threading, we can safely execute our code. After the CPU receives the interrupt signal, it will automatically transfer to execute the corresponding After the interrupt handler is completed, the execution environment of the original code will be restored and execution will continue. This method requires hardware support and is generally encapsulated by the operating system.
Polling
Loop to detect whether an event occurs, and if so, execute the corresponding handler. This applies to both low-level and upper-level development.
Windows window programs need to write the following code in the main thread, usually called a message loop:
MSG msg = { }; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); }
The message loop continuously detects whether there are messages (user's UI operations, system messages, etc.). If so, it distributes the messages and calls the corresponding callback function for processing.
One disadvantage of the polling method is that if time-consuming operations are performed in the message loop of the main thread, the program cannot respond to new messages in a timely manner. This is evident in JavaScript and will be mentioned later, along with its solutions.
However, there is no similar message loop code in JavaScript. We simply register the event and wait for it to be called. This is because the browser and Node, as execution platforms, have already implemented the event loop. JavaScript code does not need to be involved in this process. It only needs to wait quietly as the callee.
Event loop in Node
Look at the implementation of event loop through Node source code
Node uses V8 as the JavaScript execution engine and uses libuv to implement event-driven asynchronous I/O. Its event loop uses libuv's default event loop.
In src/node.cc,
Environment* env = CreateEnvironment( node_isolate, uv_default_loop(), context, argc, argv, exec_argc, exec_argv);
This code establishes a node execution environment. You can see uv_default_loop() in the third line. This is a function in the libuv library. It initializes the uv library itself and the default_loop_struct in it, and returns a pointer to it. Pointer default_loop_ptr.
Afterwards, Node will load the execution environment and complete some setup operations, and then start the event loop:
bool more; do { more = uv_run(env->event_loop(), UV_RUN_ONCE); if (more == false) { EmitBeforeExit(env); // Emit `beforeExit` if the loop became alive either after emitting // event, or after running some callbacks. more = uv_loop_alive(env->event_loop()); if (uv_run(env->event_loop(), UV_RUN_NOWAIT) != 0) more = true; } } while (more == true); code = EmitExit(env); RunAtExit(env); ...
more is used to identify whether to proceed to the next cycle.
env->event_loop() will return the default_loop_ptr previously saved in env, and the uv_run function will start the event loop of libuv in the specified UV_RUN_ONCE mode. In this mode, uv_run will process at least one event: this means that if there are no I/O events that need to be processed in the current event queue, uv_run will block until there is an I/O event that needs to be processed, or the next timer Time's up. If there are currently no I/O events and no timer events, uv_run returns false.
Next Node will decide the next step based on the situation of more:
If more is true, continue running the next loop.
If more is false, it means that there are no events waiting to be processed. EmitBeforeExit(env); triggers the 'beforeExit' event of the process, checks and processes the corresponding processing function, and jumps out of the loop directly after completion.
Finally, the 'exit' event is triggered, the corresponding callback function is executed, the Node operation ends, and some resource release operations will be performed later.
In libuv, timer events are processed directly in the event loop, while I/O events are divided into two categories:
Network I/O uses the non-blocking I/O solution provided by the system, such as epoll on Linux and IOCP on Windows.
There is no (good) system solution for file operations and DNS operations, so libuv built its own thread pool to perform blocking I/O.
In addition, we can also throw the custom function into the thread pool to run. After the operation is completed, the main thread will execute the corresponding callback function. However, Node has not added this function to JavaScript, which means that it only It is impossible to open new threads in JavaScript for parallel execution using native Node.
The above is the entire content of this article, I hope you all like it.