Node.js is a Javascript runtime, provides system capabilities of the API, mainly file, network related IO API, and IO API implementation is in Libuv, provides synchronous asynchronous two forms of API.

To explore the functionality of Libuv and the form of the API it provides.

Synchronous asynchrony, event loop

The CPU executes code sequentially, using PC registers to store the memory address of the next instruction. The flow of code execution is called control flow. However, for SOME I/O operations, the CPU does not need to perform calculations but is waiting for data to be read from disks or network devices. In this case, the CPU is idle. Therefore, if one control flow fails, the CPU usage is low. So the operating system provides the function of the process, thread, process is the unit of allocation of resources, and the executing code is mainly rely on a thread, a thread is a control flow, it is the basic unit of the CPU scheduling, that is to say, you can switch between multiple control flow, when a thread when doing the IO the release of the CPU, for other threads to run.

A blocked thread waiting for I/O is synchronous, which will waste CPU, while multiple threads switch, let other threads run on THE CPU during I/O, and then apply for CPU to continue the subsequent processing, which is asynchronous.

Asynchronism is finally realized by multithreading, but it is further encapsulated by event loop in Node.js. For example, when performing file reading and network access, developers do not need to create threads, but call API and specify callback functions, which is further encapsulated by multithreading.

The underlying implementation of asynchrony is through thread switching, but it is exposed to developers in two ways:

The first is that the developer controls the creation and destruction of a thread, specifying what the thread executes, as Java does.

The second is to provide an event loop that provides a set of asynchronous apis that are ultimately executed by threads, but the developer does not need to manually manage threads. Javascript does this.

libuv

In Node.js, event loop is implemented by Libuv, which is an asynchronous IO library responsible for file and network IO and provides an asynchronous API in the form of events.

Libuv has a thread pool that maintains the threads used to execute the asynchronous API. The size of the thread pool can be set using the environment variable UV_THREADPOOL_SIZE.

Search for UV_THREADPOOL_SIZE in the Node.js document to see this description:

Libuv is responsible for the asynchronous implementation of THE IO API, based on the lower-level OPERATING system API. Some of these operating system apis are asynchronous and some are not, and those that are not are executed asynchronously by threads in libuv’s thread pool.

These apis include:

  • All file FS API except watch API
  • Crypto’s asynchronous API
  • dns.loopup
  • All compressed Zlib apis

These apis share a thread pool, so they will definitely affect each other, so sometimes you need to increase the number of threads in the thread pool. The default is 4, and you can set this environment variable to increase the number of threads when necessary.

This reminds me of Node.js’ –max-old-space-size= size, which allows you to set the heap size, as well as performance tuning parameters.

When asking about node.js performance tuning, you can set libuv’s thread pool size and heap size as two parameters/environment variables.

After looking at the documentation, we also confirmed that the lowest level of asynchronous implementation is threads, which are encapsulated by Libuv’s thread pool. Libuv provides IO related apis, which are located in the node.js architecture as follows:

IO API in three forms

Node.js provides two apis:

The xxSync API is simple, just call the function, get the return value, many xxSync apis are synchronous, such as:

const fs = require('fs');

const data = fs.readFileSync('tmp.txt'.'utf8');
console.log(data);
Copy the code

Asynchronous apis come in two forms, callback and promise:

const fs = require('fs');

fs.readFile('tmp.txt'.'utf8'.(err, data) = > {
    console.log(data);
});
Copy the code

There’s also a version of Promise:

const fsPromises = require('fs').promises;

(async function () {
	const data = await fsPromises.readFile('tmp.txt'.'utf-8');
	console.log(data); }) ();Copy the code

The promise version has only two modules, FS and DNS, which were introduced in Node.js 10.x to facilitate the use of async and await to organize code.

const dnsPromises = require('dns').promises;

(async function () {
	const result = await dnsPromises.lookup('www.baidu.com');
	console.log(result.address); }) ();Copy the code

When using the FS and DNS modules, it is recommended to use the asynchronous API in the form of Promise, which must be node.js 10 or later.

conclusion

When the program is doing I/O, the CPU is idle. In order to make better use of the CPU, the operating system provides the function of process and thread. A thread is a control flow. When I/O is performed, switching to another thread and waiting for the I/O to finish is asynchronous, and the corresponding blocking of a thread waiting is synchronous.

Asynchronism is ultimately implemented by threads, but it is available to developers in two forms: a threading API that lets developers manage threads themselves, or an event loop that is implemented by threads in the case of asynchronous apis. The second approach is more transparent to the developer and does not require concern about thread creation and switching.

Node.js event loop is implemented in Libuv, which provides an API for file and network asynchronous IO. From the document, we can see that Libuv is implemented based on the API of the operating system, and some of the synchronous API, It is executed by libuv’s thread pool for asynchronous purposes. However, the default thread pool size is 4, which can be increased by changing UV_THREADPOOL_SIZE.

Node.js provides apis in three forms, one is synchronous, one is asynchronous callback, one is asynchronous promise. Promises: Asynchronous Promises are recommended, but only for FS and DNS modules, and must be at least node.js 10.

Hopefully this article has helped you understand the nature of asynchrony, what Libuv does, the form of the Node.js API, and how to tune Libuv.