Is Node Js Single-threaded? The Architecture of Node Js.

A guide to understanding Node Js and to write better code.

Sachin Kumar
The Startup
5 min readSep 28, 2020

--

Well, yes and no. Okay, first of all, don’t get overwhelmed with the wide range of crazy answers on this question. Also, I am not at all saying that they are wrong, but let’s see what happens under the hood.

To understand what magic the Node Js potion performs, you should be aware of its ingredients first. Node Js has two main dependencies:

  1. V8
    It provides the Javascript Engine i.e., to run JS outside the browser.
  2. libuv:
    This is a C library used to access the underlying OS features such as networking, file systems, concurrency, cryptography, etc(I/O intensive & CPU-intensive tasks). Libuv includes a thread pool for offloading work for some things that can’t be done asynchronously at the OS level.

In Node Js there are two types of threads:

  1. Event Loop: Handles the initialization & callbacks(aka: main loop, the main thread, event thread, etc).
  2. Threadpool: A pool of k Workers in a Worker Pool.

Whenever you run a js file, Node first creates a single thread and runs everything in that thread, called the Event Loop. But, when it comes across some tasks like fs.readFile() (fs is the File System module, it enables you to interact with the file system) or using crypto.pbkdf2()(crypto module provides you with cryptographic functionality that includes a set of wrappers for OpenSSL’s hash, HMAC, cipher, decipher, sign, and verify functions. pbkdf2() is one such function to hash your password), Node offloads this work to the thread pool.

That’s enough theory, let us write some code to understand it better.
Make a file called “hashing.js” and write the following code.

If you want to learn more about pbkdf2() function, go here. For now, you can just think that it hashes your password and takes a considerable amount of time to execute.

If you execute this code, you will see that it takes approximately one second to finish execution(957ms on my machine). This time might vary on your machine, but it’ll be close to this.

Output 1

So far, so good. Now uncomment line 16 and run the program again.

Output 2

This time we see that it takes approximately one second again to execute the whole program, but if Node truly was single-threaded then it would have taken two seconds to execute this. It ain’t the case though, what happens is Node offloads this work to the Thread Pool, which has four threads by default. Since my machine has two cores(two processors), both the threads can run simultaneously and hence, the execution takes one second.

Now, uncomment line 17–19 and run the program. This time you will see some amazing results.

It takes 1.7 seconds(approx, 1700ms) for the first four function calls and almost one second extra(0.8s to be specific) for the last function call.

Since the default size of threadpool is four, the first four pbkdf2()calls are assigned to all the four threads. Now, each thread gets a fair share of CPU time, and since we have four tasks, each taking approx one second to finish their execution and since we have two CPU cores, it will take a total of two seconds(approx) for all the tasks to complete their execution. As soon as one of the threads in the threadpool finishes it’s execution, it will take up the next task in the queue, pbkdf2() [hash(5)]in this case and will begin its execution.

Can you change the threadpool size?

Yes, you can.
On a Windows machine, you can do it by setting it before calling the script.

set UV_THREADPOOL_SIZE=1

In this case, you can see that it takes approximately one second(around 0.9s) for the first execution of “pbkdf2”. Since we have set the size of Threadpool to just one, it will take a total of 4.4s to execute five “pbkdf2” calls as the event loop will take only one task at a time.

On a Linux system, you can do it by writing process.env.UV_THREADPOOL_SIZE = size;at the start of your program.

Things you should cling to:

  1. Never put any heavyweight tasks in the callbacks as it is executed by the event loop. By doing so, you will block the event-loop thread and your other tasks won’t get executed, like API calls in a server.
  2. Always keep work associated with each client minimal as it ensures the maximum throughput(client request/second) of your server.
  3. Avoid vulnerable regular expression in callbacks as they are computationally expensive.
  4. Minimize the use of JSON.parse and JSON.stringify as they have a time complexity of O(n) , where n is the length of the input. So for large values of n, they can take a surprisingly long time.

References:

  1. Node Js Docs
  2. Node Js: Advanced Concepts course on Udemy

So, this is how the mysterious Node Js works behind the scenes. If this piece has helped you get a clearer picture of Node, spread a word on twitter and share with your friends as well.

--

--