Node.js is notoriously single-threaded, making it incredibly fast for I/O operations but prone to event-loop blockage under heavy CPU calculations.
To execute intensive work without blocking client requests, use native Worker Threads.
---
The Thread Scheduler Pattern
Here is a clean implementation of a thread scheduling manager that spins up child worker processes and communicates via messaging channels:
typescript
import { Worker, isMainThread, parentPort, workerData } from 'worker_threads';
import path from 'path';
if (isMainThread) {
/**
* Main Process Event Loop Context
* Dispatches computation payload to a clean system thread
*/
export function scheduleHeavyTask(payload: any): Promise<any> {
return new Promise((resolve, reject) => {
const workerPath = path.resolve(__filename);
const worker = new Worker(workerPath, {
workerData: payload,
});
worker.on('message', (result) => {
resolve(result);
});
worker.on('error', (err) => {
reject(err);
});
worker.on('exit', (code) => {
if (code !== 0) {
reject(new Error(`Worker thread terminated with exit code: ${code}`));
}
});
});
}
} else {
/**
* Background Thread Context
* Executes CPU computation isolations
*/
const computePayload = workerData;
// Simulated complex calculation
let sum = 0;
for (let i = 0; i < 1_000_000_000; i++) {
sum += i * computePayload.multiplier;
}
// Communicate results back to main thread
parentPort?.postMessage({ success: true, sum });
process.exit(0);
}---
Production Recommendations
- Avoid Over-allocation: Each
Workerthread carries memory overhead (~10MB overhead per thread). Avoid spawning a new worker for every request; instead, implement a fixed Thread Pool queue. - Channel Parity: Communication via
.postMessage()copies data using the structured clone algorithm. Minimize data payload size to avoid memory thrashing.