Home > Article > Web Front-end > A preliminary understanding of asynchronous I/O in Nodejs
This article will give you a preliminary understanding of asynchronous I/O in Nodejs. It has certain reference value. Friends in need can refer to it. I hope it will be helpful to everyone.
# The term "asynchronous" was actually born before Node. But in most high-level programming languages, asynchrony is rare. Among many high-level languages or operating platforms, Node is the first to use asynchronous as the main programming method and design concept. [Related recommendations: "nodejs Tutorial"]
Asynchronous I/O, event-driven and single-threaded constitute the keynote of Node, and the event-driven and asynchronous I/O design of Nginx and Node The concepts are relatively similar. Nginx is written in pure C, has excellent performance, and has the powerful ability to manage connections for clients, but it is still limited by various synchronization programming languages. But Node is all-round. It can be used as a server to handle a large number of concurrent requests brought by clients, and it can also be used as a client to make concurrent requests to various applications in the network.
Why asynchronous I/O is so important in Node? This is because Node is designed for the network. Under the structure, concurrency has become a standard feature in modern programming.
It is mentioned in "High Performance JavaScript" that if the execution time of the script exceeds 100 milliseconds, the user will feel that the page is stuck. Think the page has stopped responding. In the B/S model, network speed limitations cause great trouble for the real-time experience of web pages.
If the web page temporarily needs to obtain a resource and obtain it synchronously, then JavaScript needs to wait for the resource to be completely obtained from the server before it can continue execution. During this period, the UI pauses and does not respond to the user's interaction. This user experience will be extremely poor. With asynchronous requests, during the downloading of resources, the execution of JavaScript and UI will not be in a waiting state and can continue to respond to user interactions.
Similarly, the front-end can eliminate UI blocking through asynchronous use, but the speed at which the front-end obtains resources also depends on the response speed of the back-end. If a resource comes from the return of data from two different locations, the first resource consumes M milliseconds and the second resource consumes N milliseconds. If the synchronization method is used, the time consumed to obtain the two resources is M N milliseconds. In the asynchronous method, the acquisition of the first resource does not block the acquisition of the second resource, and the time consumed is max(M,N).
As the website or application continues to expand, the values of M and N will grow linearly, and asynchronous performance will be more superior than synchronous.
Assuming that there is a set of unrelated tasks that need to be completed in the business scenario, there are the following two mainstream methods:
If the cost of creating multi-threads is less than parallel execution, then multi-threading is preferred, but multi-threading requires more effort in creating threads and The overhead of thread context switching during execution is relatively large, and multi-thread programming often faces problems such as locks and state synchronization.
The disadvantage of single-threaded sequential execution of tasks is performance. Any slightly slower task will cause subsequent execution of the code to be blocked. In computer resources, I/O and CPU calculations can usually be executed in parallel, but the synchronous programming model causes I/O to wait for subsequent tasks, resulting in resources not being better utilized.
Node uses a single thread to stay away from multi-thread deadlock, state synchronization and other problems; it uses asynchronous I/O to keep a single thread away from blocking and better utilizes the CPU.
Asynchronous I/O is the most widely used in Node, but it is not original to Node.
For computer kernel I/O, asynchronous/synchronous and blocking/non-blocking are two different things. .
The operating system has only two methods for I/O: blocking and non-blocking. When calling blocking I/O, the application needs to wait for the I/O to complete before returning the result.
One of the characteristics of blocking I/O is that after the call, the call must wait until the system kernel level completes all operations before the call ends. Blocking I/O causes the CPU to wait for I/O, wasting waiting time, and the CPU's processing power cannot be fully utilized.
In order to improve performance, the kernel provides non-blocking I/O. The difference between non-blocking I/O and blocking I/O is that it will return immediately after the call. After non-blocking I/O returns, the CPU time slice can be used to process other things. At this time, the performance improvement is obvious, but due to the completion of The I/O is not completed, and what is returned immediately is not the data expected by the business layer, but only the current calling status.
In order to obtain complete data, the application needs to repeatedly call the I/O operation to confirm whether it is completed. This technique of repeatedly calling to determine whether the operation is completed is called Polling.
The existing polling technologies mainly include read, select, poll, epoll and kqueue. Here I will only talk about the polling principle of epoll.
epoll is the most efficient I/O event notification mechanism under Linux. When entering polling, if no I/O event is detected, it will sleep until an event occurs to wake it up. It truly uses event notification and execution callback methods instead of traversing queries, so it does not waste CPU and has higher execution efficiency.
Polling technology meets the need for non-blocking I/O to ensure that complete data is obtained, but for the program, it is still considered a kind of synchronization, because the application still needs Waiting for the I/O to return completely, it still takes a lot of time to wait. While waiting, the CPU is either used to iterate through file descriptors or sleep while waiting for time to occur.
Completes data acquisition by letting some threads perform blocking I/O or non-blocking I/O plus polling technology, allowing a Threads perform calculations and transfer the data obtained from I/O through communication between threads, which easily implements asynchronous I/O (although this is simulated)
But initially, Node was in *nix The platform uses libeio and libev to implement the I/O part and implement asynchronous I/O. In Node v0.9.3, a thread pool is implemented to complete asynchronous I/O.
The IOCP under Windows provides Li Xiang's asynchronous I/O to a certain extent: calling asynchronous methods, waiting for notification after I/O is completed, and executing callbacks. Users do not need to consider polling. But its internals are still based on the thread pool principle. The difference is that these thread pools are managed by the system kernel.
Due to the difference between Windows platform and *nix platform, Node provides libuv as an abstract encapsulation layer, so that all platform compatibility judgments are completed by this layer, and ensures the customization of the upper Node and the lower layer. The thread pool and IOCP are completely independent.
We often mention that Node is single-threaded. The single-thread here is just JavaScript executing in a single thread. In Node, whether it is *nix or Windows platform, there is a thread pool that completes I/O tasks internally.
The event loop, observer, and request object complete the entire asynchronous I/O link.
The event loop is Node's own execution model, which makes callback functions very common.
When the process starts, Node will create a loop similar to while(true). Each time the loop body is executed, we call it Tick. The process of each Tick is to check whether there is an event to be processed, and if so, retrieve the event and its related callback function. If associated callback functions exist, execute them. Then enter the next loop, and if there are no more events to handle, exit the process.
There are one or more observers in each event loop, and it is used to determine whether there are events to be processed The process is to ask these observers if there are events to process.
In Node, events mainly come from network requests, file I/O, etc. The observers corresponding to these times include file I/O observers, network I/O observers, etc. Observers categorized events.
The event loop is a typical producer/consumer model. Asynchronous I/O, network requests, etc. are the producers of events, constantly providing different types of events to Node. These events are delivered to the corresponding observers, and the event loop takes the events from the observers and processes them.
For Node’s asynchronous I/O calls, the callback function is not called by the developer. In the transition process from JavaScript initiating a call to the kernel executing an I/O operation, there is a product called Request object
The fs.open() method is used below as a small example.
fs.open = function(path,flags,mode,callback){ //... binding.open(pathModule._makeLong(path), stringToFlags(flags), mode, callback); }
The function of fs.open() is to open a file based on the specified path and parameters to obtain a file descriptor. This is the initial trial operation for all subsequent I/O operations. JavaScript-level code performs lower-level operations by calling the C core module.
is engaged in JavaScript calling the core module of Node. The core module calls the C module, and the built-in module makes system calls through libuv. This is the classic calling method in Node. Here libuv serves as the encapsulation layer and has two platform implementations, which essentially calls the uv_fs_open() method. During the calling process of uv_fs_open(), the parameters passed in from the JavaScript layer and the current method are encapsulated in a request object, and the callback function is set on the properties of this object. After the object is packaged, the object is pushed into the thread pool to wait for execution.
At this point, the JavaScript call returns immediately, and the first phase of the asynchronous call initiated by the JavaScript level ends. JavaScript threads can continue to perform subsequent operations on the current task.
The request object is an important intermediate product in the asynchronous I/O process. All states are saved in this object, including being sent to the thread pool to wait for execution and callback processing after the I/O operation is completed.
Assemble the request object and send it to the I/O thread pool to wait for execution. It only completes the first part of an I/O. The callback notification is the first part. Part Two.
After the I/O operation in the thread pool is called, the obtained result will be stored in the req->result attribute, and then PostQueueCompletionStatus() will be called to notify IOCP that the current object operation has been completed.
At this point, the entire asynchronous I/O process is completely over.
Event loop, observer, request object, and I/O thread pool together constitute the basic elements of the Node asynchronous I/O model.
After sorting it out, we can extract several keywords of asynchronous I/O: single thread, event loop, observer and I/O thread pool. Single thread and thread pool seem to be a bit of a paradox. Because JavaScript is single-threaded, it is easy to understand that it cannot take full advantage of multi-core CPUs. In fact, in Node, except that JavaScript is single-threaded, Node itself is actually multi-threaded, but the I/O thread uses less CPU. Also, except that user code cannot be executed in parallel, all I/O (disk I/O, network I/O, etc.) can be executed in parallel.
For more programming related knowledge, please visit: Programming Video! !
The above is the detailed content of A preliminary understanding of asynchronous I/O in Nodejs. For more information, please follow other related articles on the PHP Chinese website!