DEV Community

Cover image for Is Node.js Single-Threaded or Multi-Threaded? and Why?
Andres Reales
Andres Reales

Posted on • Originally published at becomebetterprogrammer.com

Is Node.js Single-Threaded or Multi-Threaded? and Why?

Have you been reading multiple articles trying to understand whether Node.js is single-threaded or multi-threaded? Why are there many of them saying single-threaded and others saying multi-threaded? I’ve been there and after reading one article after another, it seems there’s always a doubt in the back of your mind telling you the concept is still not clear. In this article, I hope to clarify this confusion.

According to Node.js documentation, a Node.js application runs using the event loop. The event loop is what allows Node.js to perform non-blocking I/O operations and explains how Node.js can be asynchronous. The event loop, aka the main thread, allows running one thing at a time. Having said that, Node.js JavaScript code runs on a single thread.

Now, there are several points you have probably read about in different articles such as using worker_threads making it multi-threaded, or the programming language used to develop Node.js applications makes it single-threaded, etc. I will cover those relevant points, but before we move forward, I’ll refresh your knowledge with regards to what single and multi-thread processes are.

What a Single-Threaded Process is

A single-threaded process is the execution of programmed instructions in a single sequence. Having said that, if an application has the following set of instructions:

  • Instruction A
  • Instruction B
  • Instruction C

If these set of instructions are executed in a single-threaded process, the execution would look like the following:
Single Thread

What a Multi-Threaded Process is

A multi-threaded process is the execution of programmed instructions in multiple sequences. Therefore, instructions won’t have to wait to execute unless multiple instructions are grouped within different sequences.
Multi thread

Why Node.js is Single-Threaded?

Now you know Node.js architecture is single-threaded. However, why is it single-threaded? My first question for you is, do you understand how the event loop works? If not, I recommend you check this article.

However, to keep things simple, the event loop runs one process at a time. That means it can only execute one function at a time, and since functions can have multiple instructions, the event loop will execute one instruction at a time.

At first, it sounds not efficient providing poor performance. However, quite the opposite it turns out to be more performant and scalable than other multithreaded alternatives such as Java.

Running a multithreaded solution involves leveraging multiple cores of a system. Having said that, if one thread is waiting for an I/O response, the other threads could still be in progress. In theory, multithread seems the way to go, but what we are not taking into consideration is that a thread could still be blocked regardless of other threads being available.

The beauty of the event loop is not of running everything in a single thread, but it’s available to “put aside” long time-consuming I/O operations to keep the execution of other instructions. This is the reason why we get fast responses even though we could have multiple users making requests to a Node.js API at the same time.

The first thing to clarify is there is no such thing as making requests at the same time. It is perceived to have run requests at the same time, but in reality, the event loop runs processes defined for each request based on the order in which it arrived. Let’s make this concept simple to understand by using an example. In this case, we are going to assume we have the following API endpoints:

  • /getCars
  • /updateCar
  • /updateDriver

Remember, request are not made at the same time. The event loop will handle the requests in the following order assuming that was the order they were requested:

  • /getCars
  • /updateCar
  • /updateDriver

The event loop will execute the first instructions from the /getCars endpoint. At some point, there will be an instruction which is a request from the API to a database to fetch the cars. This is considered an I/O operation. This process can take a short or long time to execute. Regardless of how fast this gets executed. The event loop will trigger this request and move it “aside” to prevent blocking the thread from executing other instructions. However, it will resume triggering the set of instructions for the /getCars endpoint once a response is sent back from the database.

Therefore, while the request made from the /getCars endpoint to the database is triggered and waiting for a response, the /updateCar endpoint will trigger its set of instructions. If there is not I/O operation within the /updateCar endpoint, the /updateCar endpoint will return a response before the /getCars endpoint returns a response.

In a similar way, if the /updateCar endpoints have an instruction to execute an I/O operation, the event loop will trigger it but won’t block the thread from executing instructions. In this way, it could either start executing the set of instructions from the /updateDriver endpoint, or resume the execution of the /getCars endpoint once it receives a response from the database. This is based on whichever is added first in the event queue.

If you think about it, the major benefit of Node.js architecture is not the fact of being single-threaded, but its ability to not block the thread from executing other instructions. This is one of the main reasons Node.js is an excellent choice for developing APIs as these are heavily based on I/O operations. The event loop’s smart system to execute intensive I/O operations and resume processes once the I/O operations are completed while not worrying about issues that can come with using multithreaded solutions such as deadlocks or race conditions makes it a no brainer for many teams to use Node.js.

Don’t Block the Event Loop (aka the Main Thread)

Like most solutions, there are advantages and disadvantages, and Node.js is not an exclusion of this. Since we know Node.js runs using the event loop, aka as the main thread, blocking the loop will indeed prevent the system from running other instructions regardless of whether they belong to a single process or multiple different processes.

Didn’t you say the event loop “triggers intensive operations and move them aside, resuming a process once the operations get a response”?

Yes.

However, it is important to clarify the event loop’s ability to “resume” an I/O operation process doesn’t mean it will be capable of getting away around with an intensive CPU operation. The beauty of an I/O operation is to use external CPU processing power to execute a process. However, if our Node.js application is the one using intensive CPU processing power to execute power, it means we cannot execute other sets of instructions until the heavy processing power instruction completes. This is called blocking the event loop.

Confusing JavaScript and Node.js Threading Process

It is important to not say Node.js is single-threaded because the JavaScript programming language is single-threaded. This is incorrect. JavaScript can run in different programming environments, and Node.js being among the most popular environments using JavaScript. Therefore, it is a common misconception to think JavaScript is single-threaded. When speaking about single-threaded or multi-threaded, we should look at how the programming environment operates rather than how the language in itself.

What About Worker Threads in Node.js? Does it Make Node.js Multi-threaded?

While the implementation of worker threads in v10.5.0 allows the use of threads that execute JavaScript in parallel, Node.js event loop architecture is single-threaded based.

What really happens when spawning multiple threads using worker_threads is the generation of multiple V8 engines sharing memory. Workers threads are useful for performing CPU-intensive JavaScript operations. This frees up the main thread’s event loop from CPU-heavy processes and keeps it available for what is best for intensive I/O operations.

The expense of generating worker threads doesn’t result in a positive impact around I/O intensive work as in the end, each thread will have the same mechanism: one event loop per thread, which won’t be any different than opting not to use worker threads. Node.js’s built-in asynchronous I/O operations are more efficient than workers can be.

Having said that, each thread will use the same Node.js architecture which is single-threaded based. You can achieve multithreading by generating multiple nodes or Node.js V8 engines which in isolation are single-threaded. It is still correct to say Node.js is not multi-threaded.

Top comments (21)

Collapse
 
codenameone profile image
Shai Almog

NodeJS isn't more performant than Java. That's a common misconception. It's got better throughput since Javas old IO uses a thread per connection which doesn't scale (but is actually pretty fast). Async Java frameworks includes the benefits of threading and the throughput of NodeJS.

This will be resolved by project Loom which is fast approaching production. Project loom essentially turns threads "partially" green. That would mean you can code synchronously in Java using threads and the simpler stream IO. But the OS won't allocate a thread per Java thread. Only to the optimum amount of threads to get the most out of your CPU.

Collapse
 
jwp profile image
John Peters

Thanks for setting the record straight. The author misspoke on true multithreading.

C# is even better it uses Tasks which always use the least busy CPU. It's not just another thread on a single process, it's an asynchronous call directly to the least busy CPU, which spins up, and queues up Task continuations. Only after the work is done are the results captured using an await. Similar to Javascript promise but many times speedier.

I have heard however that node too can spin up CPU agnostic instances. However this author omitted that discussion.

Node's async first architecture was a brilliant move and covers a lot of territory. However single node instances are nowhere the Speed of Java or C# using Tasks or other well defined threading techniques. The only problem is that browsers only speak Javascript.

Collapse
 
codenameone profile image
Shai Almog

Project loom aims to leapfrog C# by removing the need to denote tasks. It will make the JVM itself smart enough to use just the right amount of native threads seamlessly. It's currently in technology preview stage and hopefully will ship with Java 18 or 19.

Thread Thread
 
jwp profile image
John Peters

Totally cool, I never liked the Task hoops to jump through but use it long enough and it's a second language.

Collapse
 
ivan_jrmc profile image
Ivan Jeremic • Edited

Well I'm not an Java expert but every enterprise app using java like SAP products just feels slow and I instantly feel like I'm back in the 90's. Also he is saying that node is faster in this specific field which it is.

Thread Thread
 
jwp profile image
John Peters

Statement means nothing really as we're not talking about any legacy product. We're talking about what can be done today. For true parallelism today, except on mainframes, the dotnet task construct is a true co-routine design which uses all CPUs in an agnostic way. There's nothing close to it today

Collapse
 
arealesramirez profile image
Andres Reales

If it is an server running an API, there are several programming languages with their respective frameworks that would work. C# could work using .NET Core or Entity Framework just like NodeJS with Express.js

Collapse
 
arealesramirez profile image
Andres Reales

Thanks for sharing! I didn't mean to say NodeJS is more performant than Java. The main reason I was providing the explanation was to give insights with regards to why it is widely used across several projects even though NodeJS architecture runs single-threaded as it provides the performance needed to develop APIs.

Collapse
 
metcoder profile image
Carlos Fuentes

Hi @arealesramirez great article!
I would like to complement with the following:

  • JavaScript is not > Java in terms of the speed. It can in fact be more performant in terms of memory footprint, and even at some point speed, but it requires for the app to run for a time while the hot paths are being identified for optimization by V8 engine. On the other hand, Java already mades optimizations ahead of time because it optimizes during compilation, kinda the same happens with C# (typical thing between an Interpreted and a Compiled language).
  • Node.js doesn't have a single thread, in fact the JS code is executed on a single thread, you're right, but the I/O interaction happens within a thread-pool handled by libuv. This means that the Node.js process itself spawns more than one thread, but your JS code will run on a single thread thanks to V8.
  • About Worker_Threads, you're right, it spawns a V8 vm instance for executing the JS code, but have in mind that is not the same as the Cluster module, they will share the same Event Loop, and for instance the same thread-pool (libuv). Remember that the Event Loop is not within V8 engine, but rather within Libuv. Each browser has it's own Even Loop + VM engine for executing JS code. e.g. Deno uses V8 + Tokio Event engine.

Nice work! Keep it going :)

Collapse
 
coen_c7a57bc84f2f2 profile image
Coen de Wit

Please use the correct facts when referencing other environments like C# (and probably Java too on that topic). With dotnet (C#) you can choose to use AOT compilation when optimizing for startup time. But default it compiles to byte-code which during runtime is JIT compiled in multiple tiers, optimizing during the lifetime. It is even possible to create a profile from test runs to optimise before runtime.

Collapse
 
arealesramirez profile image
Andres Reales

Thanks for complementing.

As per my understanding, worker_threads will share memory but each will have each own independent event loop.

Collapse
 
metcoder profile image
Carlos Fuentes

Kinda, the worker thread can share memory with other threads (made out of the same parent thread) or the parent thread itself. But they just create a new instance of the V8 engine, not another full Node process (where the Node process is who instantiate the event_loop). In Contrast, Cluster in fact instantiates one or more new Node process, for instance they come with it's own event_loop :)

Thread Thread
 
arealesramirez profile image
Andres Reales

Oh yeah! I see what you mean. Using pm2 is a good implementation of clusters where we could have a node per each core of the cpu.
Thanks for the clarifying about the event loop!

Thread Thread
 
metcoder profile image
Carlos Fuentes

Exactly! 😄
No worries, happy to help! As I said, great work ;)

Collapse
 
aristotelesbr profile image
Aristóteles Coutinho • Edited

⚠️ I liked the way you try to clarify the matter, congratulations! I would say that having multiple instances of the V8 unable to communicate and not being fault tolerant only alleviates the problem. 😅

Collapse
 
udlose profile image
Dave Black

.NET can handle more than 7 million requests per second as of their .NET Core 2.2 back in 2019: ageofascent.com/2019/02/04/asp-net.... 8X faster than Node.js (at the time). .NET has seriously improved performance since then (as of .NET 6): https://www.techempower.com/benchmarks/#section=data-r20&hw=ph&test=plaintext&a=2 (now more than 10X faster than Node.js). Interpreted languages will likely never be able to complete with compiled languages as far as performance goes.

Collapse
 
emreaka profile image
Emre AKA • Edited

Javascript is Just in Time compiled via V8 engine but yes, still .NET is faster. C# gets JIT compiled via .NET runtime by the way.

Collapse
 
sk8guerra profile image
Jorge Guerra

The event loop link (nodejs.org/learn/the-nodejs-event-...) explanation does not exists. I think you can updated to this one: nodejs.org/en/learn/asynchronous-w....

Thanks for the great article!

Collapse
 
philleasfrogg profile image
Philleas Frogg • Edited

Your descriptions of single thread Vs multi thread is nonsense. If you have a sequence of instructions a b c, then they must be executed in that order. You can't go and process c on another thread before b. Did you mean tasks?

Collapse
 
bendev12 profile image
Onayngo Benard

Excellent article Andres.

Collapse
 
arealesramirez profile image
Andres Reales

Thanks @bendev12 !