DEV Community

Neel-Vekariya
Neel-Vekariya

Posted on

Node.js Worker Threads: Solving CPU Intensive Tasks Without Blocking the Main Thread

Have you ever noticed that your Node.js application becomes slow even though CPU usage is not very high? This can happen when a CPU intensive task blocks the main thread. A simple task like calculating a Fibonacci series for multiple users can make the application unresponsive and increase response time significantly.

What if JavaScript execute a synchronous calculation or CPU intensive task for a large number of users on the Node.js main thread. Task like calculating the Fibonacci series. This CPU intensive task blocks the event loop and makes the application unresponsive. Node.js performs this calculation on the main thread which is responsible for both synchronous and asynchronous code execution. If the main thread becomes busy in heavy calculation, it cannot efficiently handle other requests. Because of this, the whole execution process becomes slow and requests start waiting.

So the solution is to create worker threads for CPU intensive tasks. Node.js provides functionality to create worker threads and use them for heavy calculations. These worker threads perform all the CPU intensive calculations on a separate thread. This functionality removes the heavy calculation load from the main thread. During this process the main thread remains free and continues handling synchronous and asynchronous operations. This allows the application to stay responsive even when heavy calculations are running.

For applying this functionality, firstly import Worker in the main file.

const { Worker } = require("worker_threads");
const worker1 = new Worker("./worker.js", {
    workerData: 35
});
worker1.on("message", (result) => {
    console.log("Result:", result);
});
worker1.on("error", (err) => {
    console.log("Error:", err);
});
Enter fullscreen mode Exit fullscreen mode

Create a worker by providing the location of the worker file and the data required for calculation. If the calculation executes successfully, the worker returns the result. If any error occurs, it returns the error.

In the worker file:

const { parentPort, workerData } = require("worker_threads");
function fib(n) {
    // Fibonacci calculation
}
parentPort.postMessage(fib(workerData));
Enter fullscreen mode Exit fullscreen mode

Here parentPort is used for communication between the main file and worker file. workerData is used to receive data from the main thread. After completing the calculation, the worker sends the result back to the main thread using postMessage().

To understand the difference between Node.js Worker Threads and the Node.js main thread, I performed a small experiment. I created an Express server that calculates a Fibonacci(35) number for every request. Then I tested the application using Autocannon with 1000 requests and 50 concurrent connections.

First, I tested the application without worker threads. In this case the Fibonacci calculation was executed directly on the main thread. The average latency was around 4148 ms and the server handled around 11.63 requests per second. The complete test took around 86.67 seconds. During testing CPU usage remained around 27%.

After that I moved the Fibonacci calculation to a worker thread and ran the same test again. This time the average latency was around 1646 ms and the server handled around 29.42 requests per second. The complete test finished in around 34.47 seconds. CPU usage increased to around 98%.

The interesting observation was that CPU usage became much higher after using worker threads. This happened because worker threads utilized more CPU cores while the non-worker version mainly depended on the main thread. Even though CPU usage increased, the application performance improved significantly because the heavy calculation was no longer blocking the Node.js main thread.

From this experiment it is clear that worker threads are useful for CPU intensive tasks in Node.js. The biggest advantage is not only faster calculation. The main advantage is that the main thread remains free while the worker thread performs the heavy calculation. Because of this, Node.js can continue handling requests, avoid event loop blocking, and maintain application responsiveness under heavy load.

Top comments (0)