cover-image

Parallelism VS Concurrency

#parallelism
#concurrency
#javascript
#web-workers
#promises

Andrew Vo-Nguyen

March 24, 2024

3 min read

With JavaScript being a single-threaded language, it never occurred to me how limiting this fundamental flaw was until I kept hearing about how advantageous languages such as Java, Rust and Go handled concurrency. To me, firing off multiple promises in JavaScript was good enough. Not until I experienced limitations around synchronous blocking code in React and how it affected the user's experience. This is when I started to explore why a single-threaded language simply cannot handle parallel processing well.

So what is the limitation? Since JavaScript's event loop can only run on a single thread on the CPU, instructions must be pushed to the task queue. Although concurrent tasks can be executed at the same time and resolved asynchronously in the future, only one task can be handled by the CPU at a time.

What happens when we use Promise.all()? We fire off multiple promises at the same time and wait until they all resolve before we continue. This can be considered as concurrent processing (but not parallel). Technically, each promise is fired off one at a time, and only when one promise is idle (perhaps awaiting a response from an API), can we start working on the next. At no point is our CPU working on processing the promises simultaneously.

What is Parallel processing? It is when the language can take advantage of multiple CPU threads to run multiple tasks in parallel. The best analogy I can give is that concurrency is a single-lane road with multiple cars on it. If a car decides to pull over in the emergency lane, another car is free to overtake it. Parallelism is a multi-lane road with the same amount of cars. Each car operates independently of each other.

Luckily there is a famous saying in the computer science community:

Any application that can be written in JavaScript, will eventually be written in JavaScript

In 2009, an API called Web Workers allowed for multiple scripts to be executed independently of each other to take advantage of multiple threads on a CPU. This API, now implemented in all major browsers, as well as Node.js allows JavaScript to be a multi-threaded language, even though it was designed to operate on a single thread.

Naturally, I wanted to test what was the difference between sequential, concurrent and parallel execution. To set up the test, I wanted to fire off 5 promises. Each promise would await 1000ms (to simulate an artificial API call), then run a for-loop of 500 million iterations (of re-assigning a variable). The results were interesting, but not shocking to say the least.

Running sequentially on a single thread: 7.131s.
Running concurrently on a single thread: 2.994s.
Running in parallel (using Node.js web workers): 1.549s.

Needless to say, if execution time is critical, I would employ Web Workers to get the job done quickly! If time is not of the essence, the extra code may not be worth it. There is a bit of back-and-forth communication between the main thread and the worker threads.

Feel free to check out my implementation for this test on my GitHub:

https://github.com/andrewvo89/node-parallelism-concurrency