M:N is not the interesting aspect of virtual threads at all, automagically turning blocking operations into non-blocking is - which has not really been tried before (with erlang and go being the first).
GNU Pth had "automagically turning blocking operations into non-blocking" ages ago, and it wasn't the first.
I think what you probably had in mind is that C libraries of the 90s that did M:N threading didn't turn blocking operations into non-blocking?
Using blocking operations to switch contexts is really nothing new. Heck, the cooperative multi-tasking systems of the 80s (Mac, Amiga) all essentially did that for processes (not threads), and so did Unix in the 70s.
> I think what you probably had in mind is that C libraries of the 90s that did M:N threading didn't turn blocking operations into non-blocking
Yes, mostly, though my history knowledge is definitely lacking so do correct me if I’m wrong.
But you are right, there was nothing fundamentally missing, probably just no good OS support for non-blocking IO calls in the early days? Though probably the IO-CPU ratio was also different, so the benefits were not as big?
There were bad experiences with the M:N threading of the 90s in Solaris' and others' C libraries. Making those libraries make every file descriptor non-blocking behind the programmer's back was a tricky thing. Think about inheritance of file descriptors via fork() and exec() -- you could have one threaded process sharing an FD with a non-threaded process, and now even non-threaded processes' C library would have to poll(), and now add static linking with older C libraries to the mix and it just couldn't be done. So it wasn't done.
Which makes me wonder why this can be done in Java or Erlang, and the answer is that those tend to be walled gardens from which one does not fork/exec.
> automagically turning blocking operations into non-blocking is - which has not really been tried before (with erlang and go being the first).
sorry, but Haskell has had it way before Go, with proper STM too. Neither Erlang nor Go are offering the same level of ergonomics for compile-time checked M:N threading.
> M:N is not the interesting aspect of virtual threads at all, automagically turning blocking operations into non-blocking is
I'm not very into this Loom virtual threads thing, but... what's the difference between this automagically conversion of blocking into non-blocking in a M:N model and a 1:1 one? I mean, couldn't the same be done with normal threads too?
Well, to a degree this is also done by the OS, IO syscalls are frequent locations where the OS scheduler might decide to schedule another thread, but this is a very slow context switch (flushing caches, including TLB, the switch to kernel mode and back, and since heartbleed and alia it is even more expensive).
Loom implements every IO on top of a more modern async OS calls, and these virtual thread context switches are on the order of function calls, so the overhead and number of switches that can happen are much much lower.