Multithreading in javascript: Introduction to Web Workers

Urmalveer Singh - Aug 14 '23 - - Dev Community

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>
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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();
});
Enter fullscreen mode Exit fullscreen mode

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.

single worker run

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;
Enter fullscreen mode Exit fullscreen mode

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.

8 workers run

Feel free to play with the code and learn :)

. . . . . . . . . .