Node's event loop compared with the .NET thread pool

1 min read

Node.js is single-threaded for your JavaScript, not overall. One event loop thread runs callbacks, promise handlers and async/await code, one at a time. libuv has its own thread pool (default 4, UV_THREADPOOL_SIZE) for file system operations, DNS, some crypto and compression. Network I/O and timers use OS mechanisms (epoll, kqueue, IOCP) with no extra thread.

text
while (true) {    task = get_next_ready_callback(); // timers, I/O completions, promise callbacks...    run(task);                        // runs to completion, and blocks nothing else    // task must finish before the next one starts}

A callback runs to completion before the next starts, so there are no races in your JavaScript and little need for locks. A server with 10,000 connections needs one thread that starts operations and is woken as they complete.

Where it fails

CPU-bound work blocks the whole loop, including other users’ requests. The fix is worker_threads or a child process.

Compared with .NET

.NET runs code on many threads, potentially in parallel. Async I/O frees a pool thread through the state machine (see how await worksHow await works under the hoodThe compiler turns an async method into a state machine, and the thread is freed while it waits.Opens in a new tab), Task.Run gives real parallel CPU work, and shared state needs locks. Node scales across cores with the cluster module, which forks processes that each have an event loop.

Where it matters

Both avoid tying up a thread while waiting on the OS, network or disk. Neither makes I/O itself faster. See the thread poolThe thread poolA set of reusable worker threads that take jobs from a queue.Opens in a new tab.