Cpu intensive task using nodejs | Worker Thread
Created on: Jul 28, 2024
Node.js is primarily designed for I/O-bound tasks, not CPU-intensive ones. This is due to its single-threaded event loop model. However, there are strategies to handle CPU-intensive workloads effectively:
1. Worker Threads
- Introduced in Node.js v10.5.0.
- Allows you to run JavaScript code in parallel, leveraging multiple CPU cores.
- Each worker thread has its own V8 instance and memory space, preventing shared data issues.
- Ideal for CPU-bound tasks that can be broken down into smaller chunks.
2. Child Processes
- Using the
child_process
module. - Create separate processes for CPU-intensive tasks.
- Each process has its own memory space, isolating the CPU-bound work.
- Useful for long-running or independent tasks.
Example
we will test worker thread using monte carlo.
The Monte Carlo method for estimating Pi is based on the principle of geometric probability. It involves simulating random points in a square that circumscribes a quarter-circle and using the ratio of points that fall inside the quarter-circle to the total number of points to estimate Pi.
Below is a sample code in nodejs to estimate pi value.
const estimatePi = (numPoints, threadName = null) => { let insideCircle = 0; for (let i = 0; i < numPoints; i++) { const x = Math.random(); const y = Math.random(); if (x * x + y * y <= 1) { insideCircle++; } } return (insideCircle / numPoints) * 4; }; module.exports = estimatePi;
Now we will estimate pi value three times by calling method three times and using worker thread.
Without using worker thread, it take around 8 minutes.
let estimatePi = require("./montecarlo"); const numPoints = 1e10; const piEstimate = estimatePi(numPoints); console.log(`pi estimate is ${piEstimate}`); const piEstimate2 = estimatePi(numPoints); console.log(`pi estimate is ${piEstimate2}`); const piEstimate3 = estimatePi(numPoints); console.log(`pi estimate is ${piEstimate3}`);
Let's run the same using worker thread.
const { Worker, isMainThread, parentPort, workerData, } = require("worker_threads"); const os = require("os"); if (isMainThread) { const cpuCount = os.cpus().length; console.log(`cpu count ${cpuCount}`); const workerNames = ["Worker A", "Worker B", "Worker C"]; workerNames.forEach((name, index) => { const worker = new Worker(__filename, { workerData: { num: 1e10, name: name }, }); worker.on("message", (message) => { console.log(`${name} result: ${message.length}`); }); worker.on("error", (error) => { console.error(`${name} error:`, error); }); worker.on("exit", (code) => { if (code !== 0) { console.error(`${name} stopped with exit code ${code}`); } else { console.log(`${name} completed successfully`); } }); }); } else { let estimatePi = require("./montecarlo"); console.log(workerData); const result = estimatePi(workerData.num, workerData.name); console.log(`pi value is = ${result}`); parentPort.postMessage(result); }
It is taking aroung 3.3 minutes.
Remember: While these strategies can help mitigate the impact of CPU-intensive tasks, it's generally recommended to avoid them in Node.js if possible and focus on I/O-bound operations where the language excels.
You can check full code in my github repo. To understand child process, please check here