I feel like one critical misunderstanding people have is left out in between the hardware and software discussion, which is that the kernel provides certain abstractions, and everything else is built on top of that. Whether or not a program is blocking or non-blocking or multiplexed or whathaveyou is an effect depends on the layer of abstraction you are looking at. It is perfectly possible to have a programming language that is blocking that, if you strace it, is using the non-blocking kernel calls. It is perfectly possible to have an "async" layer under the hood and implement a "non-blocking" layer on top of it, and then on top of that you could easily (even accidentally!) implement a blocking layer of abstraction. It's a lot more a continuum than people realize.

I also got the sense during the height of the Node craze that some of the people who were very excited about it had the impression that Node had some sort of unique access to a "non-blocking" kernel layer or something (of course, they just weren't thinking about the kernel layer at all) that no other languages were using. In reality, if you set something like a Go program to a single OS thread for execution, you could mechanically translate a Go program into something that executes in principle identically to a Node program at the assembler level, at least in terms of what events come in and what sort of high-level code gets executed in response (vast differences in the literal instruction stream, of course). The converted Go would have a lot more "event handlers" than you'd expect because Go programs also break at function calls and a few other places (as the compiler is doing it it's easy to have lots of "event break points" that you'd never write by hand), but the compiler essentially converts the "blocking code" that uses lots of threads into "non-blocking event-based code". The difference between the two is not at the execution layer; it's at the layer you're programming. In fact pretty much everything nowadays is "non-blocking event-based code" at the execution layer, because the high-powered kernel functions that make that efficient implement event-based code, so anything else you find at a programming-language-level must be converting that abstraction into the language's abstraction. Even using kernel threads still turns into non-blocking event-based code under the hood, if you dig far enough in.

(A more clear example of how critical the layer of abstraction you are looking at is IMHO is the distinction between immutable and mutable. You can implement an immutable abstraction layer on top of mutable storage, which given that our hardware is based on mutable storage, all immutability in programming languages necessarily comes from. You can implement a mutable abstraction on top of an immutable layer with a worst-case penalty of O(log n), as in the worst case you represent memory as a immutable tree of bytes and then mutate as you would normally. Whether or not something is "mutable" or "immutable" critically depends on layer of abstraction you are looking at; it does not have a non-contingent answer, or, if it does, since our hardware is mutable the answer is that immutability does not exist at all. But while true in a sense, that answer is not as useful as one that is contingent on the abstraction layer being examined.)