Nodejs background

Let’s start with nodeJS ‘most commonly mentioned keywords: “single-threaded”, “non-blocking asynchronous IO”, “event loop”. The following is a summary of the internal principles of NodeJS and some of the problems arising from it.

Is NodeJS single threaded?

If NodeJS is a single-threaded language, you can imagine a single-instance NodeJS server accepting 100 user requests at the same time. If each user’s request takes 0.3 seconds to complete, the 100th user needs to wait 30 seconds. This is obviously not the case, so NodeJS is not purely single-threaded.

So why is NodeJS a single-threaded language? Nodejs is a single thread of javascript code.

console.log('javascript start');
setTimeout((a)= >{
  console.log('javascript setTimeout');
}, 2000);

const now = Date.now();
while(Date.now() < now + 4000) {}
console.log('javascript end');
Copy the code

Execution Result:

$ node index.js 
javascript start
javascript end
javascript setTimeout
Copy the code

In the above code, the setTimeout callback is executed within 4 seconds of the while, and the timer has already passed 2 seconds. The ‘javascript setTimeout’ line prints after ‘javascript end’, even though the timer should have been executed after 2 seconds. Because the javascript thread is not idle and cannot print ‘javascript setTimeout’, javascript code is understood as a single thread.

Asynchronous IO of NodeJS

In the example above, when 100 user requests are accepted at the same time, when IO(network IO/ file IO) operations are required, the single-threaded javascript does not stop and wait for the IO to complete, but rather “event-driven” steps in. The javascript execution thread continues to execute unfinished javascript code, and when the execution is complete, the thread is idle, as shown in the following code example.

// http.js

const http = require('http');
const fs = require('fs');

let num = 0;

http.createServer((req, res) = > {
  console.log('request id: %d, time:', num++, Date.now());
  fs.readFile('./test.txt', ()=> {
    res.end('response');
  });
}).listen(9007, () = > {console.log('server start, 127.0.0.1:9007');
});
Copy the code
// req.js
const http = require('http');

for(let i=0; i<100; i++) {
  http.get('http://127.0.0.1:9007', (res)=>{
    res.on("data",(data)=>{
      console.log('response time:'.Date.now())
      // console.log('data', data.toString())
    })
  }).on('error', (err)=>{
    console.log('error', err); })}Copy the code
Nodehttp.js // Start the serverCopy the code

Nodereq.js // Initiate 100 requestsCopy the code

It can be seen that the 100 requests are all processed in a very short time before the request is returned, while the return is all after the request, rather than waiting for each IO to be processed in order of receiving.

4. Event loop

Speaking of event loops, in the above request, 100 requests are processed in a very short time, and then each request is replied. Consider that javascript has already executed the 100th request, and the first request is replied, and the stack information of the first request is not lost. The request stack information of the first request is recorded, which is the process of registering the I/O event.

After registering events from above, the event loop is activated, and the IO of fs.readFile in the above code is actually executed. At this time, the IO execution is not related to the execution of javascript code, and the thread pool provided by the underlying NodeJS libuv receives the IO execution of the file. The default size of the thread pool is 4, which can be adjusted at startup using the environment variable process.env.uv_threadpool_size, but can not exceed 1024. It can be seen from the above that nodeJS actually has multiple processes working in parallel, instead, it uses the event loop to seal the process.

Turning to the event loop, how do you know when the fs.readfile file is read in the example above? The read operation is controlled by the thread pool. Before the thread executes, it initializes a state of “executing” in the memory where the event is registered, and the event loop has been activated to start polling and waiting for the execution result. When the thread executing the IO is finished, Then through the underlying asynchronous IO interface (epoll_WAIT /IOCP) to notify the initial registration of the task queue memory to change the state, the event cycle polling until the state becomes “completed”, at this time in the IO event registration injection callback function gets the execution right, javascript threads start work, the whole asynchronous process is finished.

You can look at the original English interpretation, the cycle of events

Translation:

** Stage overview ** Timers: This stage executessetThe Timeout () andsetI/O callbacks expire in Interval() : executes all but onesetThe Timeout (),setInterval(), close event,setOther Immediate callback functions idle, prepare: only internal use poll: obtains new I/O events, under appropriate conditions nodejs will block at this stage check:setImmediate callbacks are called here close callbacks: like socket.on("close",func) this class performs a callback to the close eventCopy the code

The above content is their own summary, there will be mistakes or understanding bias, if there are questions, I hope you leave a message to correct, so as not to mistake people, if there are any questions please leave a message, we will try our best to answer it. Don’t forget to share with your friends if it helps you! You can also follow the author, view historical articles and follow the latest developments to help you become a full stack engineer!