not everything happens in the thread pool


Node.js is an open source cross-platform JavaScript runtime for executing JavaScript outside of the browser. It is supported by the engine Google V8which makes it extremely productive.

Event Driven Asynchronous Runtime

One of the most common claims we come across when getting to know Node is that it runs on a single thread. However, one might wonder how is it possible that Node is one of the most popular tools for building fast and scalable APIs?

Technically, the fact that Node.js uses a single thread is not 100% true. Node.js actually uses many threads, but the event loop (Event Loop – which we’ll mention later) and user code run on the same thread. If we look at the documentation, we can see that Node.js uses an event-driven, non-blocking I/O model (event-driven, non-blocking I/O model), which makes it easy and efficient.

What is an event-driven non-blocking I/O model?

According to guide to Node.js, blocking methods execute synchronously, and non-blocking methods execute asynchronously. Suppose we need to write some code to read the contents of a file and print it to the console. There are two ways to do this in node: synchronously and asynchronously.

Let’s see the synchronous version first:

In the code below, the following happens: first, you need to connect the module FS. The second line calls the method readFileSyncand the result is stored in a variable data. The main Node.js thread blocks on this line until the entire contents of the file have been read. The content is then output to the console, and at the very end will be output to the console “Done”.

File Read: Synchronous Version
File Read: Synchronous Version

Now let’s see how the same code is executed asynchronously:

File Reading: Asynchronous Version
File Reading: Asynchronous Version

This example uses the method readfile, which runs asynchronously. As soon as this line is encountered, control is transferred to Libuvwhere the file is being read. This is not done on the main thread. Instead, worker threads are used from Libuv’s Thread Pool (4 threads by default). Once the read is complete, the appropriate callback is placed on the Event Queue, which is used by the Event Loop. On the next iteration of the event loop, during the execution phase of the callback (callback), this callback (callback) will be pushed onto the call stack (Call Stack) V8 and will eventually be fulfilled. All this work is done in the background and the main Node.js thread is only responsible for executing the callbacks. So, going back to the example above, the string “Done” will be printed first, and the result of reading the file will be logged after that. This is what is meant by “non-blocking I/O”, which is why every Node.js tutorial you’ve come across suggests using asynchronous methods instead of synchronous ones.

How is Node different from other web servers?

Compared to multi-threaded servers, the event-driven Node runtime behaves completely differently. On a multi-threaded server, each connection spawns a new thread to process the request, and all work done on that thread can be blocked without affecting other connections (i.e. you can query the database and wait for the result, then do some other work ). Since every processor has many cores these days, this approach makes very good use of the processor’s power. However, there are also many problems. In a multi-threaded environment, each thread adds some overhead as it requires memory, which means that the number of threads that can be used is limited. What happens if this limit is reached? The new connection will time out eventually. In addition, if the application is mostly I/O bound, each thread will spend a significant amount of time waiting for results from the network or disk. Node.js, on the other hand, handles everything on a single thread. Similar to what we explained above with file operations, the event loop acts as a dispatcher that continuously listens for new events and delegates work to the kernel or other worker threads. It never blocks (unless it is ordered to do so). So the server can accept a new client connection, then do some other work, and then keep accepting new client connections over and over again. The client connection doesn’t need a dedicated new thread, it just needs a kernel-managed socket handler. This approach is fast, lightweight, and highly scalable, which is the main reason Node.js can handle so many concurrent connections.

Node.js runtime architecture

The Node.js runtime is designed as a set of layers, with user code on top and each layer using the API provided by the layer below.

  1. Custom code: application code to JavaScriptwritten by programmers.

  2. Node.js API: a list of methods offered by Node that can be used in user code (for example, http-modules to use http-methods, crypto module, fs for file system operations, net for network requests, etc.). For a complete list of methods offered by Node, see the documentation here. Besides, here you can find source code implementation. The node API is written in JavaScript.

  3. C++ bindings and additions: Reading about Node.js you see that V8 written in C++, Libuv written in C and so on. Basically, all modules are written either in Con either C++because these languages ​​are so good and fast at solving low-level problems and using OS API. But how is it possible that the code JavaScript on the upper layers can use code written in other languages? This is what bindings do. They act as a link between two layers, so Node.js can seamlessly use low-level code written in C or C++. So what should we do if we want to add a module C++ on one’s own? First we implement the module on C++, and then write the binding code for it. This code that we write is called padding. More information can be found here.

  4. Node.js dependencies: This level represents the low-level libraries that the node uses. The biggest dependencies are the engine Google V8 and Libuv. Other libraries include OpenSSL (for SSL, TLS and other basic cryptographic functions), HTTP parser (for analysis http– requests and responses), C Ares (for asynchronous DNS-requests) and Zlib (for quick compression and decompression).

  5. Operating system: is the lowest level that represents OS API (system calls – syscalls) used by the libraries mentioned above. Because operating systems are different, the libraries include implementations for both Windows and Unix versions that make Node.js platform independent.

A few words about V8 and Libuv

Libuv is a library written in C, which is used for abstract non-blocking I/O operations. It offers the following features:

  • Event Loop

  • Asynchronous TCP and UDP sockets

  • Asynchronous DNS resolution

  • Asynchronous operations on files and the file system

  • Thread Pool

  • Child processes

  • high resolution clock

  • Streaming and synchronization primitives

  • Polling

  • Streaming

  • Pipes

V8- is a library that provides Node.js with its JavaScript engine. Namely JIT (Just-in-time) compiler. It switches continuously between compiling and executing blocks of code. The benefit of interleaving between compilation and run is that it can gather information while the code is running and, based on that information, make assumptions about what will happen in the future. These assumptions are useful for code optimization.


If you want to learn more about Node.js, I recommend the following links:

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *