Introduction: Myths and Reality of Multithreading in JavaScript
JavaScript is traditionally associated with a single-threaded execution model. Many developers, especially those coming from the world of C++ or Java, consider this a limitation. However, the modern Node.js ecosystem offers powerful tools for working with concurrency and, more importantly, with true multithreading. In this article, we will break down how the Event Loop works, when to use Worker Threads, and how to properly build high-load applications.
Unlike classical multithreading, where each thread has its own stack and can execute in parallel on different CPU cores, JavaScript in the browser and Node.js uses an event-based concurrency model. However, with the introduction of the worker_threads module in Node.js, we gained the ability to achieve true parallelism.
Main Part
1. Event Loop: The Heart of Concurrency in Node.js
The Event Loop is a mechanism that allows Node.js to perform non-blocking I/O operations despite JavaScript being single-threaded. It constantly checks the task queue and executes them as they arrive.
The main phases of the Event Loop:
- Timers — execution of callbacks scheduled via
setTimeout()andsetInterval(). - I/O callbacks — handling completed I/O operations.
- Idle, prepare — internal Node.js phases.
- Poll — retrieving new I/O events and executing their callbacks.
- Check — execution of
setImmediate()callbacks. - Close callbacks — handling close events (e.g.,
socket.on('close', ...)).
Example of Event Loop operation:
const fs = require('fs');
console.log('Start of program');
setTimeout(() => { console.log('Timer fired');}, 0);
setImmediate(() => { console.log('setImmediate executed');});
fs.readFile(__filename, () => { console.log('File read');});
console.log('End of program');
// Output:// Start of program// End of program// setImmediate executed// Timer fired// File readIt is important to understand: the Event Loop does not make code multithreaded; it only efficiently distributes time between tasks. If one task occupies the CPU for too long (e.g., a complex loop), the entire Event Loop becomes blocked.
2. The Blocking Problem and the Solution: Worker Threads
For CPU-intensive tasks (encryption, image processing, parsing large data), the Event Loop is not suitable — it will block the entire server. This is where Worker Threads come to the rescue.
Worker Threads is a Node.js module that allows creating real operating system threads. Each worker has its own instance of V8 and Event Loop, enabling code to execute in parallel.
Basic example of using Worker Threads:
// main.jsconst { Worker } = require('worker_threads');
function runWorker(workerData) { return new Promise((resolve, reject) => { const worker = new Worker('./worker.js', { workerData }); worker.on('message', resolve); worker.on('error', reject); worker.on('exit', (code) => { if (code !== 0) { reject(new Error(`Worker stopped with code ${code}`)); } }); });}
async function main() { console.log('Starting worker...'); const result = await runWorker(42); console.log('Result:', result);}
main().catch(console.error);
// worker.jsconst { parentPort, workerData } = require('worker_threads');
// CPU-intensive taskfunction fibonacci(n) { if (n < 2) return n; return fibonacci(n - 1) + fibonacci(n -