What is Node.js? NodeJS is a free-to-use, cross-platform JavaScript runtime environment that although is single-threaded in nature but uses multiple threads in the background for executing asynchronous code.
Due to the non-blocking nature of Node.js, different threads execute different callbacks that are first delegated to the event loop. NodeJS runtime is responsible for handling all of this.
Why NodeJS?
JS was originally built as a single-threaded programing language meant to run only inside a web browser. What this means is that in a process, only a single set of instructions was able to execute at a given moment in time.
The execution moved on to the next code block only when the current code block finished execution. The single-threaded nature of JS, however, made implementation easy.
In its humble beginnings, JavaScript was useful for adding only a little interaction to websites. Therefore, there was nothing that demanded multithreading. However, times have changed, user requirements have intensified, and JavaScript has become “the popular programming language of the web.”
Multithreading has now become common. Because JS is a single threaded language, achieving multithreading in it isn’t possible. Thankfully, there is a great workaround available for this situation; NodeJS.
There is no scarcity to NodeJS frameworks thanks to the popularity enjoyed by the JS runtime environment, in particular and JavaScript, in general. Before continuing with the article, let’s know take in some important points about Node.js:
- It is possible to pass messages to the forked process and to the master process from the forked process using the send function
- Support for forking multiple processes is available
- State isn’t shared between the master and forked processes
Why Fork a Process?
There are two cases when we need to fork a process:
- For enhancing the speed by delegating tasks to some other process
- For freeing up memory and unloading single process
It is possible to send data to the forked process as well as send it back.
The Way of NodeJS
Node.js makes use of two types of threads:
- The main thread handled by event loop and,
- Many auxiliary threads in the worker pool
The event loop is responsible for taking callbacks, or functions, and registering the same for execution in the future. It operates in the same thread as that of the proper JS code. Once a JS operation blocks the thread, the event loop gets blocked as well.
The worker pool is an execution model responsible for spawning and handling different threads. It synchronously performs the task and then returns the result to the event loop, which then executes the provided callback with the stated result.
To summarize, the worker pool takes care of asynchronous I/O operations i.e. interactions made with the system disk and network. Modules like the fs and crypto are the ones that primarily use the worker pool.
As the worker pool is implemented in the libuv library, there is a little delay when Node.js requires to communicate internally between JS and C++. However, this is almost imperceptible.
Everything is good until we come across the requirement of synchronously executing a complex operation. Any function that requires much time to run will result in blocking the main thread.
If the application has several CPU-intensive functions, then it results in a significant drop in the throughput of the server. In the worst-case scenario, the server will freeze and there will be no way of delegating work to the worker pool.
Areas like AI, big data, and machine learning couldn’t benefit from NodeJS due to the operations blocking the one and only main thread and rendering the server unresponsive. However, this changed with the advent of the NodeJS v10.5.0 that added support for multithreading.
The Challenge of Concurrency & CPU-Bound Tasks
Establishing concurrency in JavaScript can be difficult. Allowing several threads to access the same memory results in race conditions that are not only hard to reproduce but also challenging to solve.
NodeJS was originally implemented as a server-side platform based on asynchronous I/O. This made a lot of things easier by simply eliminating the need for threads. Yes, Node.js applications are single-threaded but not in the typical fashion.
We can run things in parallel in Node.js. However, we don’t need to create threads. The operating system and the virtual machine collectively run the I/O in parallel and the JS code then runs in a single thread when it is time to send the data back to the JavaScript code.
Except for the JS code, everything runs in parallel in Node.js. Unlike asynchronous blocks, synchronous blocks of JS are always executed one at a time. Compared to code execution, a lot more time is spent waiting for I/O events to occur in JS.
A NodeJS application simply invokes the required functions, or callbacks, and doesn’t block the execution of other code. Originally, neither JavaScript nor NodeJS was meant to handle CPU-intensive or CPU-bound tasks.
When the code is minimal, the execution will be agile. However, the heavier the computations become, the slower the execution gets.
If you still tried to accomplish CPU-intensive tasks in JS and Node then it resulted in freezing the UI in the browser and queuing any I/O event, respectively. Nonetheless, we have come a long way from that. Now, we have the worker_threads module to save the day.
Multithreading Made Simple With The worker_threads Module
Released in the June of 2018, Node.js v10.5.0 introduced the worker_threads module. It facilitates implementing concurrency in the popular JavaScript runtime environment. The module allows creation of fully functional multithreaded NodeJS apps.
Technically, a worker thread is some code that is spawned in a separate thread. To begin using worker threads, one requires to import the worker_threads module first. Afterward, an instance of the Worker class needs to be created for creating a worker thread.
When creating an instance of the Worker class, there are two arguments:
- The first one provides a path to the file, having .js or .mjs extensions, containing the worker thread’s code and,
- The second one provides an object containing a workerData property that contains the data meant to be accessed by the worker thread when it begins execution
Worker threads are capable of dispatching more than one message event. As such, the callback approach is given preference over the option of returning a promise.
The communication among the worker threads is event-based i.e. listeners are set up to be called as soon as the event is sent by the worker thread. 4 of the most common events are:
worker.on('error', (error) => {});
- Emitted when there is an uncaught exception inside the worker thread. Next, the worker thread terminates, and the error is made available as the first argument in the provided callback.
worker.on('exit', (exitCode) => {})
2. Emitted when a worker thread exits. exitCode would be provided to the callback if process.exit() was called inside the worker thread. The code is 1 if worker.terminate() terminates the worker thread.
worker.on('message', (data) => {});
3. Emitted when a worker thread sends data to the parent thread.
worker.on('online', () => {});
4. Emitted when a worker thread stops parsing the JS code and begins execution. Although not commonly used, the online event can be informative in specific scenarios.
Ways of Using Worker threads
There are two ways for using worker threads:
- Approach 1 – Involves spawning a worker thread, executing its code, and sending the result to the parent thread. This approach necessitates for creating a new worker from scratch every time for a new task.
- Approach 2 – Involves spawning a worker thread and setting up listeners for the message event. Every time the message is fired, the worker thread executes the code and sends the result back to the parent thread. The worker thread remains alive for future use.
Approach 2 is also known as worker pool. This is because the approach involves creating a pool of workers, putting them on waiting, and dispatching the message event to do the task when required.
Because creating a worker thread from scratch demands creating a virtual machine and parsing and executing code, the official NodeJS documentation recommends employing the approach 2. Moreover, approach 2 is more efficient than approach 1.
Important Properties Available in the worker_threads Module
- isMainThread – This property is true when not operating inside a worker thread. If needs be, then a simple if statement can be included at the start of the worker file. This ensures that it runs only as a worker thread.
- parentPort – An instance of MessagePort, it is used for communicating with the parent thread.
- threadId – A unique identifier assigned to the worker thread.
- workerData – Contains the data included in the worker thread’s constructor by the spawning thread.
Multiprocessing in NodeJS
In order to harness the power of a multi-core system using Node.js, processes are available. The popular JS runtime environment features a module called cluster that provides support for multiprocessing.
The cluster module enables spawning multiple child processes, which can share a common port. A system using NodeJS can handle greater workloads when the child processes are put into action.
Node.js for the Backend
Internet has already become the platform of choice for millions, if not billions, around the world. Therefore, to push a business to its maximum potential and leave no stone unturned in the cause of the same it is mandatory to have a robust online presence.
It all starts with a powerful, intuitive website. To make an impeccable website it is important to choose the best frontend and backend technologies. Although single-threaded in nature, Node.js is a top choice in the cause of backend web development services.
Despite having an abundance of multi-threaded backend options, reputed companies like to opt for NodeJS. This is because Node.js offers the workaround for using multithreading in JavaScript, which is already “the most popular programming language of the web.”
Conclusion
The worker_threads module offers an easy way of implementing multithreading in Node.js applications. A server’s throughput can be significantly enhanced by delegating CPU-heavy computations to the worker threads.
With the newfound support for multithreading, NodeJS will continue to reach more and more developers, engineers, and other professionals from calculation-heavy fields such as AI, big data, and machine learning.
Looking to add a calendar to your website? Check out these top JavaScript calendar plugins!
Comments