JavaScript: real parallelism with worker threads
Split an array between two worker threads and add their partial results.
On this page
JavaScript promises and Node.js modules.
- Split an array between two worker threads and add their partial results.
- Predict the sample output and explain the state changes.
- Complete the practice task and check the boundary cases.
The concept
To use more than one core, Node.js provides worker_threads. Each Worker runs its own JavaScript engine with its own memory, so workers do not share ordinary variables. They communicate by messages: data passed in workerData or sent with postMessage is copied (or transferred).
This program is both parent and worker. isMainThread tells which role the current copy has. The main thread starts two workers on the same file, each gets half the numbers, and each posts back its sum. Because nothing is shared, there are no data races. The messages are the synchronization.
Code example
const { Worker, isMainThread, parentPort, workerData } = require("node:worker_threads");
if (isMainThread) {
const data = Array.from({ length: 1000 }, (_, i) => i + 1);
const halves = [data.slice(0, 500), data.slice(500)];
const jobs = halves.map((part) => new Promise((resolve, reject) => {
const worker = new Worker(__filename, { workerData: part });
worker.on("message", resolve);
worker.on("error", reject);
}));
Promise.all(jobs).then((sums) => {
console.log(sums.join(" ") + " total " + sums.reduce((a, b) => a + b, 0));
});
} else {
parentPort.postMessage(workerData.reduce((a, b) => a + b, 0));
}125250 375250 total 500500
Follow the code
Open the walkthrough to inspect the example one step at a time.
Show guided walkthrough
const { Worker, isMainThread, parentPort, workerData } = require("node:worker_threads"); if (isMainThread) { const data = Array.from({ length: 1000 }, (_, i) => i + 1); const halves = [data.slice(0, 500), data.slice(500)]; const jobs = halves.map((part) => new Promise((resolve, reject) => { const worker = new Worker(__filename, { workerData: part }); worker.on("message", resolve); worker.on("error", reject); })); Promise.all(jobs).then((sums) => { console.log(sums.join(" ") + " total " + sums.reduce((a, b) => a + b, 0)); });} else { parentPort.postMessage(workerData.reduce((a, b) => a + b, 0));}Follow the example
Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.
View the complete dry-run table
| Step / line | What happens | State after step | Output so far |
|---|---|---|---|
| The same file runs twice: once as the main thread and once in each worker. | — | — | |
| Start a worker thread and copy its half of the data to it. | 2 workers running | — | |
| In the worker: sum the numbers received and post the result back. | 2 workers running | — | |
| Combine the two partial sums: 125250 + 375250. | total = 500500 | 125250 375250 total 500500 |
Behind the scenes
How JavaScript processes this code
A JavaScript engine parses and runs the source. A function body runs when the function is called. console.log writes to the console.
Cost and common mistakes
O(n) work in total; ideal elapsed time O(n / p) on p cores plus start-up cost.
- Starting a worker costs time and memory. For small jobs it is slower than doing the work on the main thread.
- Copying large data to a worker is expensive. SharedArrayBuffer and transferable ArrayBuffers avoid copies.
- A worker that throws sends an error event. Handle it or the failure is lost.
Try it yourself
How would you use four workers?
Reveal answer and reasoning
Split the array into four slices, start four workers, and wait for the four messages with Promise.all.