Parallelism in JavaScript: build super programs🔥🔥

Sk - Sep 7 '21 - - Dev Community

Concurrency vs Parallelism

updated and concise article here

concurrency:

single object performing multiple tasks( example: a juggler)

we already talked about this system in the previous chapter: the task queue and microtask queue which are both executed by a single thread (interchanged), the main thread.

both async and sync code in JS is executed by a single thread, which juggles both of them based on the state of the event loop.

Concurrency example


 // doing multiple tasks in a period of time

task a task b  task c

 concurrency:  
        task a

        task c

        task a

        task b

        task a

        task c - complete 

        task b

        task a

        task b

        task a - complete 

        task b - complete

        final result

Enter fullscreen mode Exit fullscreen mode

a single thread juggles multiple tasks, giving the illusion that they are happening at the same time.

parallelism

multiple objects working at the same time, on one or multiple tasks


task a   task b    task c 

task a   task b    task c 

task a   task b    complete

task a   complete  complete

task a   complete  complete

complete   complete  complete



final result


Enter fullscreen mode Exit fullscreen mode

Multiple independent objects, working independently of each other(not interleaved) this is usually achieved through multiple threads and cores, languages such as java have this feature built in I believe.

Parallelism in browsers

Browsers are fundamentally single threaded, having only the main thread handling both the execution of JavaScript code and rendering the browser window, async programming does relieve the main thread by pausing execution of specific code, but in the end even that code will run on the main thread, needless to say the main thread works pretty hard, which is actually the source of "a script is slowing down your browser" message, when a script is taking to long to finish a task and blocks the main thread, while async is the solution, an even better solution is creating a new thread and that is where web workers come in.

web workers

a web worker creates/spawns a second JS thread separate from the front end browser, the thread does not have access to the DOM, window and anything in the front-end browser accept given by the main thread, all the is, is JS, this is true parallelism: the idea of two separate threads not inability to access the DOM, these threads run at the same time without blocking each other.

they communicate via a message system, they are able to send messages to each, which can be strings, objects or simple values.

This way we can migrate heavy computation from the main thread to the 2nd, and allow the main to perform it's primary duty to handle use input and react seamlessly.

This is a true game changer, you can literally perform heavy tasks in the worker, without the browser missing a frame, this is ultimate optimization.

getting started with workers

because workers run in the browser we need an HTML file for this part,

create three files:


 index.html
 main.js
 worker.js


Enter fullscreen mode Exit fullscreen mode

I will be using vscode live server plugin to serve index.html, you can use whatever you like, or even a bundler like parcel which support imports and live reload.

Goal: create a second thread running an infinite loop, while the browser's main thread plays animation at 60FPS.


<!DOCTYPE html>

<html lang="en">

<head>

     <meta charset="UTF-8">

     <meta http-equiv="X-UA-Compatible" content="IE=edge">

     <meta name="viewport" content="width=device-width, initial-scale=1.0">

     <title>Document</title>

</head>

<body>



 <label id="label"></label>



 <script src="main.js"></script>

</body>

</html>





Enter fullscreen mode Exit fullscreen mode

in main.js:

// main thread



/**

 * @type {HTMLLabelElement}

 */

const label = document.getElementById("label")




const skills = ["react", "vue", "angular", "ionic", "nativescript", "html", "css", "sass"]



// simple DOM update
setInterval(() => {

    // choosing a random skill every 16ms and updating the label element to show that skill

     let rand = Math.floor(Math.random() * skills.length - 1);

     label.innerText = skills[rand]

}, 16);


Enter fullscreen mode Exit fullscreen mode

I know this does not seem much, given that set interval is a microtask, but if we add an infinite loop in the main file, one of two things will happen your browser will trash or not update the UI at all, since the main thread is stuck in this infinite loop, because of the run-to-completion rule, you can test it by adding an infinite loop in main.js


while(true){



}

Enter fullscreen mode Exit fullscreen mode

this sets us up nicely to prove that a worker spawns a new thread separate from the browser window and document, if we can run an infinite loop logging something in the worker thread while updating the browser successfully every 16ms this will prove that these threads are separate,

remove the infinite loop in main and add the following on top

// creates a worker thread(spawning a new thread)
// Worker() takes name of an existing js file, which the worker will load in it's own environment 
// separate from the the main js and it's thread 
// every code in worker.js will run in the second thread
const worker = new Worker("worker.js")


// we use the worker object to communicate and receive communcication from the second thread


// sending a msg to the second thread
// the msg can be an object, stringified JSON object, buffer arrays etc
// but you cannot send DOM elements, classes etc 

worker.postMessage("hello there")


Enter fullscreen mode Exit fullscreen mode

open worker.js


//worker.js thread


//catching/receiving messages


// self = refers to the worker, 
// listening to messages

self.onmessage = e => {


  // logging the recieved message
 console.log(e.data)



 // sending back a message to the main thread after 10 seconds
 setTimeout(()=> {

 // sending a message to main thread 

 postMessage("after 10 000 milliseconds")

 }, 10000)


}




Enter fullscreen mode Exit fullscreen mode

In main.js we can also listen to messages from the second/worker thread using the worker object

worker.onmessage = e => {

 console.log(e.data, "from second thread")

}

Enter fullscreen mode Exit fullscreen mode

if you reload, in the console you will see worker.js logging "hello there" and after 10000ms the main thread will receive a message from worker and logs it

the infinite loop experiment

in the worker


self.onmessage = e => {
...
}


let index = 0;



// infinite loop
while(true){



 // logging at an interval, logging at every iteration will crash the browser
 if(index % 10000000000){

 console.log("while loop")

 }




 index += 0.00000000000000000000000000000001;

}



Enter fullscreen mode Exit fullscreen mode

magic, the browser is not skipping a bit, while the infinite loop is running, if you have been using JS for a while, you'll understand how much of a big deal this is, just having a while(true) statement in JavaScript is super impressive.

the browser might crash because of the frequent console logs, make sure you clear the console while it is running.

Using this simple architecture there are many possibilities: operating on big files, large amounts of data and algorithms, only sending the computation result to the main thread.

In term of the DOM access, there are libraries out there, for one workerDom which allows manipulation of the DOM in the worker, workerDom also works well with major front-end frameworks.

With that we have achieved true parallelism in JavaScript.

This is an excerpt from an eBook JavaScript for advanced beginners available on gumroad as a pre-order, and should be launching soon,

The eBooks main goal is to provide a gentle but needed push towards advanced JS, range of topics are covered from Object Oriented JS, Object composition to generators, promises, computational media and metaprogramming

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .