Thread Pool and OS Operations
Overview
Node.js executes javascript code using a single thread. Therefore the execution of only one line may be performed at a time. However, Node js can be multithreaded since it includes hidden threads in js via the libuv module, which handles tasks like network requests and file reading from the disc. Node.js introduces the worker threads module, which allows us to start and run several jobs concurrently. These worker threads distribute CPU-bound tasks to a large number of workers for optimization.
Introduction
Threads are like a small process: they have their instruction pointer and can conduct one Javascript task at a time. Threads reside within a process’s memory, and they don’t have their memory. The execution of threads is similar to that of processes.
Javascript is single-threaded by default which means only one thread is available to perform all the operations. This was the main drawback of the javascript which led to the implementation of server-side Asynchronous I/O, which would reduce the race among the thread in a multithreading environment.
Lets us now see how javascript has evolved. Javascript has introduced a new concept to prevent the limitation we are facing, that is, Web Workers. Web Workers (threads in js) are mainly used to perform CPU-intensive Javascript tasks. They can perform long-running tasks without affecting or blocking the main execution thread.
Using a Constructor, we can create a worker object, which will run a js file. This js file includes code to run the worker thread; these workers run in some other global context that is different from the current window. However, Node.js built-in asynchronous I/O operations are more efficient than Workers can be. Web workers are not of much help with I/O-intensive work.
Node JS Runtime Environment
Node js is related to JavaScript as it provides an environment that allows the execution of your JavaScript code outside of the browser. Furthermore, Node js is open-source software that was released to the public in 2009 at a JSConf by Ryan Dahl, which quickly became the most popular tool for constructing servers and IoT-related stuff.
The Node.js runtime environment consists of several parts. You must be knowing about Google Chrome’s V8 engine, which takes the responsibility for the execution of the javascript code written by you. Other than the V8 engine, the runtime environment also consists of a library called Libuv. Libuv is responsible for handling all the asynchronous I/O operations. In a nutshell, Node js is the V8 engine, and a few libraries like Libuv manage I/O operations.
These operations related to I/O are also referred to as heavy duty jobs in the context of the operating system. Libuv handles tasks like file and folder management, TCP / UDP transactions, compression, encryption, and so on. While the majority of these tasks are designed to be asynchronous, there are a handful that is synchronous and, if not handled appropriately, might cause our apps to be stopped. This is the reason why the Libuv includes a Thread Pool.
Asynchronous execution is supported by the Node js runtime. As a result, it does not wait for tasks to be completed and sends them to a specific thread for execution. It then begins processing the next request. As a result, Node js servers are very scalable when compared to others. Node js does not buffer data either but instead processes the entire data in chunks. This greatly improves the speed.
Another advantage is that Node js comes bundled up with a package manager, NPM. NPM or the Node Package Manager contains all of the Node modules you may require to develop your application. The Node js runtime comes with several uses. The fact that it is widely used raises concerns about its performance. This brings us to the why and what of the thread pool.
You must be knowing about the main thread and the event loop. Many developers are unaware that we can add multithreaded features to our Node program. However, even though Node js supports asynchronous activities, some synchronous jobs block the main thread until they are done. There are still certain synchronous jobs that block the main thread until they are done. The libuv offers a pool of additional threads for some synchronous processes from which it can distribute CPU workloads.
Disadvantages of Node JS Runtime Environment
- CPU-intensive Programmes: are not handled efficiently. Node.js is unsuitable because it is event-based and single-threaded, and thus is not efficient enough to handle CPU-intensive projects. Concurrent requests such as generating music, video, or altering images, , for example,, are not managed by Node.js.
- Insufficient Maturity. Numerous third-party modules that have previously been created by the community are accessible to developers using Node. However, the entire ecosystem is still in its infancy. Because of the flaws and inconsistent versions, it is unsuitable for the maintenance staff.
- Simplicity:. PHP is quite straightforward in comparison to Node.js. PHP is only sufficiently complex to allow for the creation of simple applications.
- There is no Client App Required:. The best results come from simply sending the data in HTML form. PHP is intended to accomplish this. In this particular situation, PHP has an advantage over Node.js.
- Coding Speed:. The majority of developers believe that Node js is faster for developing web applications. However, when it comes to quickly put together a project, PHP is unquestionably the best choice.
Thread Pool
Node js has a few dependencies that offer some specific features. V8, llhttp, libuv, c-ares, and OpenSSL are among them. The libuv library is a library written in the C language that was created for the abstraction and handling of asynchronous, non-blocking I/O operations.
These operations include:
- Asynchronous file operations
- Asynchronous DNS resolution
- Child process
- Signal handling
- Named pipes
- Timers
- Asynchronous TCP and UDP sockets
- Thread pooling
The Libuv library is in charge of providing multithreading to Node.js, or the ability to create a pool of threads in a Node js process for synchronous activities to ride on. The thread pool is made up of four threads that handle heavy-duty operations that should not be handled by the main thread. And, with this configuration, these jobs do not obstruct our application. If the main thread is made to handle all these operations then the application may halt.
The thread pool is used by the following APIs:
- dns.lookup()
- All synchronous zlib APIs
- All fs APIs that are synchronous except fs.FSWatcher()
- Asynchronous crypto APIs
You can further categorize the above list into CPU-intensive and I/O-intensive operations. Hence we can say that the libuv is one of the reasons why Node js applications have high scalability. If our Node js application had only the event loop, then the CPU-intensive and I/O-intensive operations would have caused frequent halts in the application as they would always block the main thread.
If we perform a file compression in the event loop, then it will cause our program to struggle to the death. To deal with this, libuv will simply start a new thread. When accessing a file system asynchronously also, a new thread is required. This is because this I/O-intensive activity will make the main thread slow and blocked. Synchronous file systems, on the other hand, are usually handled on the main thread.
This libuv package allows us to raise the number of threads from 4 to 1024. This is because anytime one of the APIs or processes executing in any of the four threads takes longer than expected, the performance of the other threads suffers. Libuv starts a thread pool of four threads to which it offloads synchronous activities. By doing so, Libuv ensures that synchronous tasks do not block the execution of our application unnecessarily.
You can take advantage of this configuration setting to improve the performance of your application. You can achieve the change in several threads by accessing and modifying the UV_THREADPOOL_SIZE Node variable.
File I/O by Libuv
A global thread pool is used by libuv to implement file I/O, allowing all loops to queue work on it. It enables the use of disc in an abstracted asynchronous approach. To enable behavior similar to async, it decomposes complex operations into simpler operations.
Example: In most cases, if software asks to write a buffer to a specified file, the I/O will be stalled until the operation is finished or successful without libuv. However, libuv covers this in an async manner by introducing an event notification that would notify of the operation's success or failure when it is completed; until that time, the other I/O operations can be performed without trouble.
Note: libuv does not guarantee thread safety. (with few exceptions)
In contrast to event loops, File I/O employs platform-independent techniques.
File I/O handles three different types of async disc APIs:
- Posix AIO (supported by BSD, Linux, Solaris, Mac OS X, AIX, etc)
- Linux AIO (supported in the kernel)
- Windows overlapped I/O
Other Libuv Features (High-Resolution Clock, Signal Handling )
-
Asynchronous DNS Resolution: Name resolution is enabled by the node:dns module. You could, for instance, seek host names and IP addresses using it. Although it bears the term Domain Name System (DNS), lookups do not always follow the DNS protocol. To resolve names, dns.lookup() makes advantage of the operating system's resources. It might not require any network interactions at all. Use dns.lookup() to execute name resolution in the same way that other apps on the same system do.
-
Asynchronous File and File System Operations: Simple wrappers over POSIX-compliant functions are used by Node to implement File I/O. The Node File System (fs) module can be imported using the following syntax:
Both synchronous and asynchronous variants are available for every method in the fs module. Asynchronous methods accept an error as their first parameter and a callback function for the completion as their final parameter. Asynchronous methods are preferable to synchronous methods since the former never blocks a program while running, whereas the latter does.
-
ANSI Escape Code Controlled TTY: tty.ReadStream and tty.WriteStream classes are provided by the node:tty module. Direct use of this module won't be required or feasible in the majority of situations. However, it can be accessed using:
Process.stdin will by default be initialized as an instance of tty when Node.js determines that it is being run with a text terminal (TTY) attached. By default, ReadStream, as well as process.stdout and process.stderr, will be instances of tty.WriteStream.
-
IPC with Socket Sharing, using Unix Domain Sockets or Named Pipes (Windows): On Windows, the node:net module enables IPC via named pipes, and on other operating systems using Unix domain sockets.
-
Child ProcessesThe Node: child process module allows you to spawn subprocesses like but not identical to popen(3). The child process.spawn() function is principally responsible for this functionality.
-
Thread Pool: All heavy lifting is delegated to a pool of worker threads by Libuv. The file I/O and DNS lookup are handled by the thread pool. However, all callbacks are executed on the main thread. As with Node 10.5, the programmer can additionally use worker threads to run Javascript concurrently. Libuv utilises four threads by default, however, this can be modified with the UV THREADPOOL SIZE environment variable.
-
Signal Handling: Signals are an intercommunication technology based on POSIX (The Portable Operating System Interface). When Node receives signal events, these events will be transmitted. A notification is issued to tell the recipient that an event has happened. The signal name will be received by the corresponding signal handler. The signal name is in uppercase of the name of the event. , for example,, SIGTERM for SIGTERM signals Syntax:
-
High-Resolution Clock: When utilizing the POSIX timer APIs, the High-Resolution Timers system allows a user space application to be woken up from a timer event with more precision. If Date.now isn't precise enough, process.hrtime can be utilised.
-
Threading and Synchronization Primitives: The npm package async-mutex implements synchronisation primitives, enabling you to control asynchronous events deterministically.
-
Asynchronous TCP and UDP Sockets: UDP datagram sockets are implemented via the node
module. The node module offers an asynchronous network API for constructing TCP or IPC servers and clients (net.createServer() and net.createConnection()).
Conclusion
- Threads reside within a process’s memory, and they don’t have their memory.
- Web Workers (threads in js) are mainly used to perform CPU-intensive Javascript tasks.
- Web Workers can perform long-running tasks without affecting or blocking the main execution thread.
- The Node.js runtime environment consists of Chrome's V8 engine and libuv library.
- The libuv offers a pool of additional threads for some synchronous processes from which it can distribute CPU workloads.
- The libuv library is a library written in the C language that was created for the abstraction and handling of asynchronous, non-blocking I/O operations.
- The Libuv library is in charge of providing multithreading to Node.js, or the ability to create a pool of threads in a Node js process for synchronous activities to ride on.
- This libuv package allows us to raise the number of threads from 4 to 1024.
- Other features of libuv include Asynchronous DNS resolution, Asynchronous file and file system operations, IPC with socket sharing, using Unix domain sockets or named pipes (Windows), Signal handling, high-resolution clock and Asynchronous TCP and UDP sockets.