Problem
As we mentioned above Javascript is single threaded and it will executes the code in sequence order. Here will be the problem when user trying to access two HTTP requests which one of it is handling CPU intensive task
const express = require('express')
const app = express()
app.get("/normal", async (req, res) => {
res.send(`normal request`)
})
app.get("/slow", (req, res) => {
const startTime = new Date()
const result = mySlowFunction(req.query.number)
const endTime = new Date()
res.json({
result,
time: endTime.getTime() - startTime.getTime() + "ms",
})
})
function mySlowFunction(baseNumber) {
let result = 0;
for (let i = Math.pow(baseNumber, 5); i >= 0; i--) {
result += Math.atan(i) * Math.tan(i);
};
return result
}
app.listen(process.env.PORT || 5000, () => console.log('server is running at port 5000'))
As we can see, when Node.js server is handling intensive CPU task, other non-intensive HTTP request will get affected as well. To resolve this, we can use child_process
module to handling it by multiprocessing
What is child_process
The child_process module provides the ability to spawn new processes which has their own memory. The communication between these processes is established through IPC (inter-process communication) provided by the operating system.
There are total of 4 main methods under this module which are:
child_process.fork()
In this article, we will be only focus on the fork method to handle the intensive CPU task.
fork method takes in 3 arguments: fork(<path to module>,<array of arguments>, <optionsObject>)
The child_process.fork() method able to spawn new Node.js processes. It allow an additional communication channel built-in that allows messages to be passed back and forth between the parent and child by using process.send and process.on. So the parent process won’t be blocked and can continue responding to requests.
main.js
const express = require('express')
const app = express()
const { fork } = require('child_process')
app.get("/normal", async (req, res) => {
res.send(`normal request`)
})
app.get("/forkProcess", (req, res) => {
const startTime = new Date()
const child = fork('./longComputation') //access the module path in fork method
child.send({ number: parseInt(req.query.number) }) //pass custom variable into the particular file
//Execute the following code when the child response has responded back
child.on('message', (result) => {
const endTime = new Date()
res.json({
result,
time: endTime.getTime() - startTime.getTime() + "ms",
})
})
})
app.listen(process.env.PORT || 5000, () => console.log('server is running at port 5000'))
longComputation.js
function mySlowFunction(baseNumber) {
let result = 0;
for (let i = Math.pow(baseNumber, 5); i >= 0; i--) {
result += Math.atan(i) * Math.tan(i);
};
return result
}
process.on('message', message => {
const result = mySlowFunction(message.number)
process.send(result)
process.exit() //terminate process after it's done
})
Result
Explanation
-
main.js
- Import the
fork
method from child process - Passing the file path into the
fork
method argument - Send custom variable into separated nodejs process (forked) by using process.send
- Respond back to the HTTP request after receiving the child process response by process.on
- Import the
-
longComputation.js
- Relocate the high intensive CPU task code into separate file
- Execute the high intensive code when receiving the signal/ message from parent process by using
process.on
and return once the task is finised by usingprocess.send()