DEV Community

Urmalveer Singh
Urmalveer Singh

Posted on

Multithreading in javascript: Introduction to Web Workers

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 :)

Top comments (2)

Collapse
 
luisguillermobultetibles profile image
luisguillermobultetibles • Edited

Amigo, puedes utilizar esta clase casi terminada para crear y ejecutar tareas en paralelo en javascript, te agradecería si pudieras embellecerla de modo que funcione el evento de devolución del resultado, pues parece que dominas bien, el tema:

`
class Thread {

static results = [];

constructor(task = () => {
  return 'Ok';
}) {
  this.addTask(task);
}

addTask(arrowFunction) {
  this.id = this.generateId();
  this.code = arrowFunction;
  this.workerURL = this.createWorkerURL(`(${arrowFunction.toString()})();`);
  this.worker = new Worker(this.workerURL);
  this.startingTime = new Date();
  this.endingTime = null;
  this.status = null;
  this.result = null;
  this.worker.onmessage = (event) => {
    console.log('Punto de control 1: ', event);
    this.result = event.data;
    this.status = 'completed';
    this.endingTime = new Date();
  };
  this.worker.postMessage('start');
}

getResult() {
  return {id: this.id, result: Thread.results[this.id], status: this.status};
}

cancel() {
  this.worker.terminate();
  URL.revokeObjectURL(this.workerURL);
}

state(id) {
  return {id: this.id, status: this.status};
}

generateId() {
  return Math.random().toString(36).substr(2, 9);
}

createWorkerURL(code) {
  const blob = new Blob([code], {type: 'application/javascript'});
  return URL.createObjectURL(blob);
}
Enter fullscreen mode Exit fullscreen mode

}

// Arrancando las tareas

const task1 = new Thread(() => {
console.log('Tarea 1 ejecutándose...');
return 'Resultado tarea 1';
});

const task2 = new Thread(() => {
console.log('Tarea 2 ejecutándose...');
return 'Resultado tarea 2';
});

const task3 = new Thread(() => {
console.log('Tarea 3 ejecutándose...');
return 'Resultado tarea 3';
});

// Cómo eliminar una tarea?

const canceled = task1.cancel();
console.log('Tarea cancelada:', canceled);

// Y cómo conocer el estado (es aquí dónde el perro no sigue al amo)

const taskState = task2.state();
console.log('Estado de la tarea:', taskState);
`
Saludos., Bultet, Sábado 4 de octubre de 2023, Cuba.

Collapse
 
oggy107 profile image
Urmalveer Singh

thanks :>