Node.js is not single threaded

Gottcha! 😱

If you were anything like me, you would’ve come here thinking:

What the heck is he talking about?! Of course Node.js is single threaded!

But that’s only partially true! Bare with me.

TL;DR

This article is not about workers, clusters nor any other technique to actually achieve something that feels like concurrency. But rather the low-level implementations of Node.js which make use of multi threading outside of the call stack and outside of JavaScript.

To be fair

Node.js’s Main Thread is indeed single threaded, simply because it’s plain JavaScript. There’s no other way.
This very thread is the one that’s executing all user written code (and the place where the Event Loop is doing its magic!)

Yet there are also a lot of moving parts of Node.js that aren’t necessarily single threaded.


V8 and libuv

Since it’s implemented on top of Google’s V8 and libuv Node.js isn’t at all “JavaScript only” but rather a really great composition of JS and C++.

V8 is the engine that compiles JavaScript code into C++ and then (when seeing native modules like “fs”, “crypto”, “http”, …) utilizing a library called libuv which gives easy access to the underlying operating system, including its multi threading capabilities.

Note: libuv is actually also the library that implements the infamous Event Loop.

From here we go multi threaded

As soon as we’re entering this level of Node.js we’re possibly acting on more than just one thread!

Going from here we’re about to act on OS level and therefore we can of course leverage multi threading!
libuv is managing something that is called a “thread pool”.
Each of those threads in this pool can get assigned (by the event loop) a specific task from your Event Queue and work on it concurrently.

While you have limited abilities to control anything going on in the pool or thereafter, because this is already taking part in OS Scheduler city, you can actually control the size of the pool simply by using environment variables in your JS code.

// to create 5 threads that could takle concurrent tasks just set:
process.env.UV_THREADPOOL_SIZE = 5;
// 4 threads are by default in the pool
// max. number you can set is 128

So, where can I see the multi thread magic?

It’s already happening all the time! Depending on the OS you’re running Node on different modules are being executed concurrently already. E.g.: hashing using the pbkdf2 function from the “crypto” module.
const { pbkdf2 } = require('crypto');
const start = Date.now();
const doExpensiveHashing = () => {
pbkdf2('pwd', 'salt', 100000, 512, 'sha512', () =>
console.log(`Done in ${Date.now() - start}ms`);
);
};
doExpensiveHashing(); // Done in 938ms

Alrighty, this one takes a bit less than a second to finish.
So, in a truly single threaded world this means if I call the “doExpensiveHasing” function 4 times it would take a total time of about 4 seconds (each one about one second), right?

Let’s see than.

const { pbkdf2 } = require('crypto');
const start = Date.now();
const doExpensiveHashing = () => {
pbkdf2('pwd', 'salt', 100000, 512, 'sha512', () =>
console.log(`Done in ${Date.now() - start}ms`)
);
};
doExpensiveHashing(); // Done in 937ms
doExpensiveHashing(); // Done in 942ms
doExpensiveHashing(); // Done in 943ms
doExpensiveHashing(); // Done in 948ms
WHUAT?!

Jup, you just saw multiple threads kicking in inside of a Node.js application. 
To prove that it would be different if it actually executed on one thread only, we can change our environment variable:

process.env.UV_THREADPOOL_SIZE = 1;
{
...
}
doExpensiveHashing(); // Done in 936ms
doExpensiveHashing(); // Done in 1865ms
doExpensiveHashing(); // Done in 2813ms
doExpensiveHashing(); // Done in 3765ms
That is what things look like on a single thread!

As you can see we JavaScript developers have to be careful when claiming Node.js would be entirely single threaded because de facto it isn’t. 
And this is actually a good thing.
Imagine having the power of asynchronous code but only executing it in a single thread.

Knowing this seems to be part of really understanding Node.js and hence writing performant code and leveraging its real powers.

Thank you for reading!
Please let me know if there is something unclear or wrong. 🤓