How Node.js handle CPU intensive tasks.

Dev Niklesh
3 min readFeb 8, 2021

--

Node.js is well-known for data intensive task but, because of its single threaded architecture, computation demanding tasks block the main thread keeping the server busy from handling other requests until the task completes.With latest versions, Node.js can execute these tasks using child-process and free-up main thread. Let’s see how it is done the in following tutorial.

Functions offered by child-process library are,

  1. exec() & execFile() — to run terminal commands from inside node app.
  2. spawn() — to run terminal commands and stream the result in chunks.
  3. fork() — to execute other node.js programs.

In this tutorial, we will be focusing on the most used fork() method.

Feel free to jump directly to solution section.

Problem:

Let’s recreate the problem we are trying to address here.

File app.js,

var app = require('express')();app.get('/mycount', async (req, res) => {     var myCount = await deadSlow();     console.log("Sending /mycount result");     res.send(myCount);});app.get('/happy', async (req, res) => {     console.log("Sending /happy result");     res.send({          msg: "Hello World"     });});async function deadSlow() {     let count = 0;     while(count < 5000000000) {           count += 1;     }     return { totalCount: count };}app.listen(3000, () => console.log('Server running on port: 3000'));

This is a simple express server with 2 routes,

  1. /mycount — Computation intensive function. Simply increments the count 5 Billion times.
  2. /happy — Quick function

Run this project,

$ node app.js

When you hit /mycount and /happy one after the other in 2 different terminals, you can observe that the result from /happy is delayed because deadSlow() has blocked the main thread with CPU intensive task.

In terminal 1,

$ curl http://localhost:3000/mycount

In terminal 2,

curl http://localhost:3000/happy

In your node console, (/mycount result is sent first before /happy)

Sending /mycount resultSending /happy result

Solution:

Now, let jump right into the solution and move the CPU intensive function out into a getMyCount.js file like this,

File getMyCount.js,

function deadSlow() {     let count = 0;     while(count < 5000000000) {           count += 1;     }     return count;}process.on('message', (keyword) => {     if(keyword === 'START_COUNT') {          console.log('Child process starting the count');          let count = deadSlow();          process.send(count);     }});

Modify app.js like this,

const app = require('express')();const { fork } = require('child_process');
app.get('/mycount', async (req, res) => { var child = fork(__dirname + '/getMyCount.js'); child.on('message', (myCount) => { console.log('Sending /mycount result'); res.send({ totalCount: myCount }); }); child.send('START_COUNT');});app.get('/happy', async (req, res) => { console.log("Sending /happy result"); res.send({ msg: "Hello World" });});app.listen(3000, () => console.log('Server running on port: 3000'));

Now, when you hit “/mycount” and “/happy” one after the other in 2 different terminals, you can see that “/happy” result is returned quickly with no delay.

In your node console, (/happy result is sent first before /mycount)

Sending /happy resultSending /mycount result

What happened now?

  1. We import the fork() from “child_process” library (this is a built-in library, no need to install via package manager).
  2. A new child process is started when “/mycount” router is called.
  3. The communication between parent (app.js) and child process (getMyCount.js) is enabled using global “process” variable.
  4. child.send(“START_COUNT”) — this passes the keyword to the event listener process.on(“message”) which in turn executes the deadSlow() functions for us. (Parent to Child)
  5. process.send(count) — this passes the result count to event listener child.on(“message”) which in turn returns the result to the user. (Child to Parent).
  6. This helps keep main thread unblocked and free to handle other requests. (in our case /happy request is handled with ease)

Cool isn’t it.

Summary

Use fork() from child_process library to handle CPU intensive tasks. Keep a not on the keyword and execute the required function accordingly.

If you would feel I needed improvement in my explanations anywhere, please let me know in the comments below. Please read other blogs of mine and follow me for more advanced node.js related tips for production ready apps.

See you in my next tutorial.

--

--

Dev Niklesh

MEAN Stack Developer hoping to land on moon building my own Rocket!