Not everything goes to worker threads in Node.js

You may have heard that Node.js uses worker threads to perform operations such as reading a file. But did you know that not every operation goes to worker threads?

"Did you know that there are asynchronous non-blocking operations at the operating system level that Node.js uses directly without worker threads?"

System calls are traditionally blocking

System calls, such as reading a file, are traditionally blocking. First the call is initiated, then it waits for necessary operations at the operating system level to take place. When the operating system side is complete the results are passed on and the function will return.

There is possibly a large time in between initiating the call and actually getting the results. The time in between is spent waiting. The executing thread is halted and nothing else takes place.

Make use of waiting time

The guiding principle behind Node.js is to make use of the time spent waiting for an operation to complete. Other requests should be advanced while one request is waiting on something. In order to make this happen, a blocking operation has to be made non-blocking.

One way of making a blocking system call non-blocking is to perform it in a separate thread. This way the main thread is free to continue working on other things as the blocking operation is blocking a completely different thread. This is the solution Node.js uses for blocking system calls.

Non-blocking system calls

Figure 1. Node.js leverages non-blocking system calls directly without worker threads

Not every system call is blocking. Asynchronous processing model is known to be performant. And this fact is know at the operating system level, too.

There are non-blocking system call implementations available for some operations. They aren't widespread and available for every possible operation, but there are some. And Node.js can make use of these.

Similar to JavaScript, a non-blocking call is separates its initiation and passing of results. Whereas a callback is used in JavaScript, a different more pro-active approach called polling is used at the operating system level.

What goes to worker threads

The current state of non-blocking system calls varies greatly across platforms. Node.js is faced with the difficult task of trying to provide unified operation across all of these.

For some operations Node.js can leverage non-blocking operations uniformly across platforms. This is the case for network I/O. For some a worker thread approach is still used. This is the case for filesystem operations.

The following table describes in more detail which operation goes to worker threads and which don't.

Operation Main thread Worker thread
Filesystem x
Pipe x
UDP x  
Table 1. Operations that are performed in main thread using asynchronous non-blocking system calls or in worker threads.

Database drivers and other 3rd party libraries

Some things were not included in the table. Database drivers, message queues and other 3rd party libraries may be built with combination of native C++ addons and JavaScript code. Such libraries rely on custom code that may be partly in worker threads or only leverage the main thread. The safest choice way to find out is to turn to the module authors.

In the end

Node.js uses worker threads to perform blocking operations. This way, the time spent waiting for I/O to complete can be used to advance other requests. However, there is growing support for asynchronous operations at the operating system level. It varies greatly across platforms but for some operations such as network I/O it is there. For such operations, Node.js can leverage non-blocking system calls directly without using worker threads.

Write Asynchronous Programs course
Share article: