We often say that Node.js is single-threaded, which can be very misleading to newcomers. Node.js programs are not “single-threaded”, and the code is as follows:

let end = Date.now() + 1000 * 10
for (; Date.now() < end;) {}
Copy the code

After running, search for Node in the activity monitor and the result is as follows:

See, a Node program has seven threads. At this point, you’re probably wondering, what the hell is going on? The correct answer is:

Single-threaded means that JavaScript execution is single-threaded, but the JavaScript hosting environment is not single-threaded.

How to understand this sentence? When you run an application with Node xxx.js, the operating system starts the following seven threads:

  • One Javascript main thread is used to execute user code

  • One watchdog monitoring thread is used to process debugging information

  • One V8 Task Scheduler thread is used to schedule task priorities and speed up delay-sensitive tasks

  • Four V8 threads are used to perform background tasks such as code tuning and GC

So, the threads in our Node program are actually: the threads required by the JavaScript host environment + the JavaScript execution threads.

Does that make you feel like you’re in the middle of something? Don’t worry, I’ll add one more line:

require('fs').readFile(require.main.filename, () = > {})
let end = Date.now() + 1000 * 10
for (; Date.now() < end;) {}
Copy the code

The results of the active monitor at this time are as follows:

WTF? Why are there 4 more threads? What’s going on here? The reason appears in the first line of code:

require('fs').readFile(require.main.filename, () = > {})
Copy the code

This line of code is asynchronous, and Node is an “asynchronous non-blocking” language. If it’s still 7 threads, that means the main JavaScript thread is also performing I/O operations, causing a block. So libuv creates a thread pool, and by default there are four threads in the pool, so we see 11.

In other words, all the asynchronous operations in the code are taken over by the thread pool, and the JavaScript main thread is not involved, but just executes the synchronous code. Here is the Node process structure:

If all the code is synchronous, only 7 threads will be started, and if there are asynchronous I/O operations, 11 threads will be started by default. Why is it “default”? The capacity of uv_thread_pool can be changed by setting the environment variable UV_THREADPOOL_SIZE. For example, the following code changes the thread pool capacity to 1:

process.env.UV_THREADPOOL_SIZE = 1
require('fs').readFile(require.main.filename, () = > {})
let end = Date.now() + 1000 * 10
for (; Date.now() < end;) {}
Copy the code

At this point, there are only eight threads:

Therefore, node.js programs are not single-threaded, but the main thread is single-threaded. All asynchronous I/O operations are handled by threads in libuv’s thread pool, and the results of the run are notified to the main thread via callbacks.