Node is, essentially, non-multithreaded. The asynchronicity goes deeper than Node, deeper than libuv, and even deeper than the facilities (epoll
, kqueue
, IOCP
, etc.) that libuv uses.
When the kernel gets an async request it does not fire up another thread of execution. Instead, it adds it to a simple list of "things to watch out for." If a process makes a network read request, for instance, the kernel will make an entry on that list. It's something like "hey, next time there is a read request that looks like this, let the process know about it." After making this entry, the kernel returns control back to the process and both go on their merry way. The only thing that survives is the data on the list.
The kernel is informed of a network read event via hardware interrupts. Using an interrupt, the processor yanks the kernel into a special loop -- stopping anything it's doing at the moment -- and tells it about the event. The kernel then checks against its list of outstanding requests, and (in the kevent AIO case) sends a similar interrupt (in the form of a signal) to the process to let it know about the network read. So, no threads. Just interruptions.
Well, this is a bit of a simplification: in the non-AIO kevent and epoll cases, after the kernel gets a network read it'll just put it on an event list. The process periodically checks that event list to see if something came in for it.
Also, from the kernel view, this is how all I/O works. The big difference is that the kernel isn't requiring the process to wait for the kernel to get back to it.
There is actually a little more complexity in libuv as non-network requests (and DNS requests, which are special, painful forms of network requests) are handled by threads. This is because the kernel facilities for making those asynchronous are generally not so great, if they exist at all.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…