Multithreading in javascript: Introduction to Web Workers
Urmalveer Singh
Posted on August 14, 2023
As we all know javascript is single-threaded, meaning that it can only ever execute one piece of code at a time. When an asynchronous event occurs (like a mouse click, a timer firing, or an XMLHttpRequest completing) it gets queued up to be executed later. In other words, just single thread handles the event loop. While this design simplifies programming, it also introduces limitations, especially when tackling resource-intensive tasks. Computationally intensive tasks, causes applications to become unresponsive or slow. This is where the need for Web Workers becomes evident.
Web Workers
Web Workers are browser-based threads that enable parallel execution of tasks, allowing heavy computations, data processing, and other resource-intensive operations to occur in the background. Workers run on a separate thread than the main execution thread. Data is sent between the main thread and workers through messages. This separation of tasks into multiple threads not only enhances performance but also maintains the responsiveness of the user interface.
Setting Up Web Workers
setting up web workers is pretty easy. Lets get into it.
1. Setting up project folder and adding HTML
create a project folder and add index.html
in the root of it.
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Web Workers</title>
<script src="main.js"></script>
</head>
<body></body>
</html>
2. create javascript file named main.js
in the same folder
main.js
contains the main code which will create new web workers and assign tasks to them. We will create an array of numbers from 1 to 200_000
and calculate sum of all previous numbers as we iterate through it just to mimic complex work. We will also measure the performance gains when using 1
worker vs when using 8
workers.
const msToS = (ms) => {
return Number(ms / 1000).toFixed(4);
};
const chunkify = (list, size) => {
const chunks = [];
for (let i = size; i > 0; i--) {
chunks.push(list.splice(0, Math.ceil(list.length / i)));
}
return chunks;
};
const run = (work, workers) => {
const chunks = chunkify(work, workers);
const tick = performance.now();
let completedWorkers = 0;
console.clear();
chunks.forEach((chunk, i) => {
const worker = new Worker("./worker.js");
worker.postMessage(chunk);
worker.addEventListener("message", () => {
console.log(`Worker ${i + 1} completed`);
completedWorkers++;
if (completedWorkers == workers) {
console.log(
`${workers} workers took ${msToS(
performance.now() - tick
)} s`
);
}
});
});
};
const main = () => {
const list = Array.from({ length: 200_000 }, (_, i) => i + 1);
const workers = 1;
run(list, workers);
};
main();
3. create javascript file named worker.js
in the same folder
This file contains the actual work to be done by web worker. We will add message event listener and pass an array (single chunk) from run()
method in main.js
.
self.addEventListener("message", (e) => {
const list = e.data;
for (item of list) {
let sum = 0;
for (i = 0; i <= item; i++) {
sum += i;
}
}
self.postMessage("done");
self.close();
});
Running Web Workers
Let us start with single web worker and see how much time it takes to complete the work. Make sure workers
variable is set to 1
in run()
method in main.js
. Run the project in any browser in open developer console. You will see that single worker took more than a minute to complete the work.
Now lets bump up the workers to 8
. set workers
variable to 8
in run()
method in main.js
. It should look like this
const workers = 8;
Run the project once more and observe the performance gains. You will see that 8
workers completed the work in about 18
seconds. This is about 72% performance gain than single worker. Isn't this amazing.
Feel free to play with the code and learn :)
Posted on August 14, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
September 27, 2024