Multithreading In JavaScript using Web Workers

Photo by Shannon Potter on Unsplash

Multithreading In JavaScript using Web Workers

Pros and Cons of multithreading and How to Optimize them.

Ashish maurya
·Aug 14, 2022·

5 min read

Subscribe to my newsletter and never miss my upcoming articles

Play this article

Table of contents

  • Problem with Single threaded Programing Language.
  • Modern Problem Required Morden Solution
  • Things to remember while using Web-Worker
  • TL;DR

Have you ever heard that Javascript is a single-threaded language and can only execute one process at a time well that is completely true. JavaScript uses an event loop to execute the process, for now, think of the event loop is as a queue where all the Javascript processes kept inside and are executed one by one.

In older Browsers, the whole browser was executed on Only one thread but now Each tab of the browser is given a thread to execute the process on.

 Javascript EventLoop

Problem with Single threaded Programing Language.

As JavaScript is a single-threaded Programing language so now only one Process can be executed in one tab. You will have a better understanding of this if you are familiar with Event Loop in Javascript.

For normal purposes, It is more than enough but for the CPU-intensive process, this can cause major issues in the UI. Suppose you want to do a certain CPU-intensive task and while doing it another process will stop working such as clicking or scrolling or anything else you do on the website.

Here is an example where I calculate a sum of all the numbers in the range using a for loop.

let sum = 0;
  for (let i = 0; i < 10000000000; i++) {
    sum += i;
  }
  alert("The sum is: " + sum);

Let's see what happens in the browser if I run this.

webWorker.gif

let's understand what happening here In this Gif. Here are two Buttons one is to change the background color of the Website and one is to do CPU-intensive work which is to calculate the Sum and once done it just shows the sum on the screen as the gif.

Now before I clicked on the Calculate sum button I was able to change the background of the Website but once I click on the Calculate Sum the javascript will only execute any other process once it calculate the other. As executing the calculated Sum takes a lot of time it just freezes all the other processes.

Well, this can be solved by using something called web workers.

Modern Problem Required Morden Solution

An Introduction to Web Workers

MDN in their official document says and I quote

Web Workers are a simple means for web content to run scripts in background threads.

Meaning you can now run your Javascript Script on a different thread in the background. Which is exactly the solution we are looking for.

Let's try to do that using a web worker, as a web worker needs a new script so will create a new script for that let's call that a worker.js, and in our main.js let's initiate the worker script using the single line new Worker("worker.js")this is how easily a new worker can be initiated in your already written javascript.

Our main.js will look something like this

// main.js 

const calculateSum = document.getElementById("calculateSum");
const changeColor = document.getElementById("changeColor");

const worker = new Worker("worker.js");
calculateSum.addEventListener("click", function () {

// We posted a message to the worker with a simple JSON as we don't require to pass any data
  worker.postMessage({ text: "sum" });

// Our worker is listening to the return message then it will execute some logic on the basis of data passed.
  worker.onmessage = function (e) {
    document.getElementById("sum").innerHTML = `the sume is ${e.data}`;
    worker.terminate() //terminatiing the worker after the use
  };
});

changeColor.addEventListener("click", function () {
  document.body.style.backgroundColor =
    document.body.style.backgroundColor == "red" ? "blue" : "red";
});

now as you can see I have moved the logic to calculate the sum from the main.js to the worker.js

// worker.js

onmessage = (e) => {
  let sum = 0;
  for (let i = 0; i < 10000; i++) {
    arr[i] = i;
  }
  postMessage(sum);
};

this will post the sum back and the Website will work without any blockage. You can easily try it.

Things to remember while using Web-Worker

Even though workers are amazing but there are some trade-offs which we will talk about in this Section.

With great power come's the great Responsibility

Web Workers use message passing which is performance costly. The message is copied instead of share which is exactly the reason for the performance cost.

Let's use performance.now() web API to get the performance of the same function in the main thread and web-worker thread and let's watch the performance.

I changed the code according to our use case. Here are the new codes

// main.js

const calculateSum = document.getElementById("calculateSum");
const changeColor = document.getElementById("changeColor");

const worker = new Worker("worker.js");

threadedFunction = () => {
  worker.postMessage({ text: "sum" });

  worker.onmessage = function (e) {
    // document.getElementById("sum").innerHTML = `the sume is }`;
    worker.terminate();
  };
};

MainThread = () => {
  let start = performance.now();
  let sum = 0;
  let arr = [];
  for (let i = 0; i < 10000; i++) {
    arr[i] = i;
  }
  let end = performance.now();
  console.log(`Main thread took ${end - start} ms to complete`);
};

calculateSum.addEventListener("click", function () {
  MainThread();
  threadedFunction();
});

changeColor.addEventListener("click", function () {
  document.body.style.backgroundColor =
    document.body.style.backgroundColor == "red" ? "blue" : "red";
});
// worker.js

onmessage = (e) => {
  let start = performance.now();
  let arr = [];
  let sum = 0;
  for (let i = 0; i < 10000; i++) {
    arr[i] = i;
  }
  postMessage(arr);
  let end = performance.now();
  console.log(`Worker took ${end - start} ms to complete`);
};

Here are the results for the above benchmarks on my Pc with an i7 processor.

image.png

Well, this might come as a shock to you because we are actually running the same code than why it is almost 2x slow.

image.png

Debugging the Performance

Let's use the performance profiler to actually see what is taking so long for it to execute the same program in the worker thread.

As you can see the message takes almost 0.5ms whereas the original function call only took 1.5ms.

OnMesage Time

Function Call

In Web Worker the more complex the data you pass the more time it will take to pass as it copies the whole data.

There are various ways to optimize the performance which we will talk about in the next session.

TL;DR

  1. Javascript is a Single Threaded Language but you can use multithreading in javascript using web-workers in the browser.
  2. Web-Workers are easily initiated using new Worker() and you can pass data from the main thread and worker thread using onmessage() method and postMessage() method.
  3. Complex Data takes more time to pass as workers do not share the data but instead copy them.
 
Share this