📜 ⬆️ ⬇️

Visually about the execution thread in Node.js

In the comments to my previous topic about asynchronous programming, callbacks and the use of process.NextTick (), Node.js asked a lot of questions about how a great performance is obtained or can be obtained by using non-blocking code. I will try to demonstrate this visually :) The article is intended mainly to clarify some aspects of the work of Node.js (and libeio in its composition), which can be difficult to describe in words.

Example of processing requests by the server with blocking reading:


First, I will comment on the usefulness of using non-blocking I / O. As a rule, using blocking operations in Node.js is only at the stage of application initialization, and not always. Proper error handling will in any case require using try / catch, so code using non-blocking operations will not be more complicated than using blocking operations.
It only needs to be remembered that there may be more requests for non-blocking operations than libeio threads. In this case, new requests will queue up and block execution, but this will be transparent to the programmer.

Example of processing requests by a server with non-blocking reading:

')
Of course, these two examples show the case when server performance is maximized. However, the benefit of non-blocking reading is at any time between incoming requests, even in the worst case, to improve productively due to the involvement of libeio threads in the processing of requests.
The total request processing time (the time between the client sending the first request and receiving the last processing result, the blue number on the right) will be less anyway, if there are enough threads for all requests. But even in the worst case, this time will not exceed the processing time when using synchronous reading.

An example of reducing the processing time when two requests arrive almost simultaneously:


And here we come to the most illogical technique that is used by Node.js programmers and can cause confusion for most developers. If the I / O takes most of the time to process the request, then the rest of the code should not be optimized. However, the time to receive data from memcached can be commensurate with the execution time of the application business logic and template making. And if you use caching or a database in the memory of the Node.js process ( Dirty or Alfred ), then the time to work with the database may be less than the time of the rest of the application. Therefore, to split the code into separate parts and call callbacks use process.nextTick ():

// blocking callbacks function func1_cb(str, cb) { var res = func1(str); cb(res); } function func2_cb(str, cb) { var res = func2(str); cb(res); } // non-blocking callbacks function func1_cb(str, cb) { var res = func1(str); process.nextTick(function () { cb(res); }); } function func2_cb(str, cb) { var res = func2(str); process.nextTick(function () { cb(res); }); } // usage example func1_cb(content, function (str) { func2_cb(str, function (result) { // work with result }); }); 

When using this approach in separating the execution of calc (1) and calc (2), the total processing time for the previous example with almost simultaneous arrival of requests does not change, however, the first request will be returned to the client later.

An example of “harm” from process.nextTick () with the almost simultaneous arrival of two queries:


However, this is the worst case in terms of the applicability of process.nextTick (). In case requests come rarely, as in the first example considered, there will be no harm from process.nextTick () at all. In case requests come at an “average” frequency, the use of process.nextTick () will speed up the processing of requests due to the fact that the initial processing of a new request and the start of a non-blocking read can interfere at the moment the flow is interrupted. This reduces both the total processing time and the average processing time for a single request.

An example of the "benefit" of process.nextTick ():


Let's summarize a little topic. First, when using Node.js, non-blocking I / O is worth using. It is desirable even in cases where not the standard number of libeio threads is used, but less, or with a large number of incoming requests. problems can be removed using caching and in-process DB, and the performance will not be much different from the use of other parallelization technologies. Secondly, the use of process.nextTick () "on average" can improve server performance, and in general it is more beneficial than harm.

UPD (02.02): Slightly improved circuitry. The source code is available at: github.com/Sannis/papers_and_talks/tree/master/2011_node_article_async_process_nexttick .

Source: https://habr.com/ru/post/112977/


All Articles