Node.js (node.js)

  • 原文: Overview of Blocking vs. non-blocking
  • Translator: Fundebug

To ensure readability, free translation rather than literal translation is used in this paper.

This blog post will cover Blocking and non-blocking of Node.js. I’ll mention Event Loop and Libuv, but not knowing about them won’t hurt your reading. The reader only needs to have some basic JavaScript knowledge and understand the Node.js callback pattern.

I/O is mentioned many times in the blog, which mainly refers to the use of Libuv to interact with the system’s disks and network.

Blocking

Blocking means that some node.js code has to wait until some non-Node.js code has finished executing before it can continue. This is because the Event Loop cannot continue when a block occurs.

For Node.js, poor code performance due to CPU-intensive operations is not considered blocking. Blocking occurs when you need to wait for non-Node.js code to execute. Synchronization methods in Node.js that rely on libuv (ending in Sync) cause blocking, which is the most common case. Of course, some native Node.js methods that don’t rely on Libuv can also cause blocking.

All I/ O-related methods in Node.js are provided with asynchronous versions that are non-blocking and can specify callback functions, such as fs.readfile. Some of these methods also have blocking versions with function names that end in Sync, such as fs.readfilesync.

Code sample

Blocking methods are executed synchronously, while non-blocking methods are executed asynchronously.

Take reading a file as an example. Here is the code for synchronous execution:

const fs = require('fs');
const data = fs.readFileSync('/file.md'); // Until the file is read, the code blocks and no further code is executed
console.log("Hello, Fundebug!"); // The file will not be printed until it has been read
Copy the code

The corresponding asynchronous code is as follows:

const fs = require('fs');
fs.readFile('/file.md', (err, data) => {
  if (err) throw err;
}); // The code will not block because of the read file and will continue to execute the following code
console.log("Hello, Fundebug!"); // The file will be printed before reading it
Copy the code

The first example code looks much simpler, but it has the disadvantage of blocking code execution, with subsequent code waiting until the entire file has been read.

In synchronous code, if there is an error in reading the file, the error requires a try… Catch, or the process crashes. For asynchronous code, it is up to the developer to handle the error of the callback function.

We can modify the sample code slightly, and here is the synchronization code:

const fs = require('fs');
const data = fs.readFileSync('/file.md'); 
console.log(data);
moreWork(); // console.log will be executed later
Copy the code

The asynchronous code is as follows:

const fs = require('fs');
fs.readFile('/file.md', (err, data) => {
  if (err) throw err;
  console.log(data);
});
moreWork(); // before console.log
Copy the code

In the first example, console.log will be executed before moreWork(). In the second example, since fs.readfile () is non-blocking, the code continues to execute, so moreWork() is executed before console.log.

MoreWork () does not wait for the entire file to be read and can continue executing, which is the key to node.js increasing throughput.

Concurrency and throughput

Js code execution in Node.js is single threaded, so the Event Loop can execute the callback function after executing other code. If you want your code to execute concurrently, you must keep the Event Loop running for all non-javascript code such as I/O.

As an example, suppose each request to the Web server takes 50ms to complete, of which 45ms is the database I/O operation. If database I/O is performed asynchronously in a non-blocking manner, 45ms can be saved to process other requests, which can greatly improve the throughput of the system.

The Event Loop approach is different from that of many other languages, which typically create new threads to handle concurrency.

Mixing blocking and non-blocking code can be problematic

When dealing with I/O, we should avoid the following code:

const fs = require('fs');
fs.readFile('/file.md', (err, data) => {
  if (err) throw err;
  console.log(data);
});
fs.unlinkSync('/file.md');
Copy the code

In the example above, fs.unlinksync () is probably executed before fs.readfile (), which means that the file was deleted before we could read file.md.

To avoid this, we should use non-blocking methods to ensure that they are executed in the correct order.

const fs = require('fs');
fs.readFile('/file.md', (readFileErr, data) => {
  if (readFileErr) throw readFileErr;
  console.log(data);
  fs.unlink('/file.md', (unlinkErr) => {
    if (unlinkErr) throw unlinkErr;
  });
});
Copy the code

In the example above, we put the non-blocking fs.unlink() in the callback function of fs.readfile ().

reference

  • libuv
  • About Node.js

About Fundebug

Fundebug focuses on real-time BUG monitoring for JavaScript, wechat applets, wechat games, Alipay applets, React Native, Node.js and Java online applications. Since its launch on November 11, 2016, Fundebug has handled more than 1 billion error events in total, and paid customers include Google, 360, Kingsoft, Minming.com and many other brands. Welcome to try it for free!

Copyright statement

Reprint please indicate the author Fundebug and this article addresses: blog.fundebug.com/2019/06/12/…