I've worked on several thread-per-core systems, including embedded systems in which each thread owns a core and does not make system calls or context switch. You can do that, and it's ideal performance-wise, but it does not apply to multi-process systems -- how else are they to coordinate workflows, than through kernel-visible concurrency constructs: i.e., threads?
In language implementations with built-in user-mode schedulers like Go and Erlang, you get closer to this ideal for, as they will spin up a thread per core for CPU-intensive workloads. But even there, blocking system calls are still the norm (at least with Linux, which is only very recently moving toward a generic async system call system, with io_uring), requiring many more threads per core to saturate I/O workloads. And that still doesn't address the multi-process coordination problem.
But green threading doesn't address the OP's issue of "too many threads": a process using green threads needs to spawn one thread per CPU if it plans to use the CPU's full power.
On say, a 16-core system like the OP's, you're back to 16 threads per process -- on average, more than the OP was complaining about (15).
And it means backtraces in gdb work.
But now you have more than one application on the machine? Or even, code from multiple vendors which all need to issue I/O concurrently? How do you integrate their work with your custom scheduler?
You need some centralized, system-wide scheduler everyone agrees on.
That's what threads are.
Currently every app that uses tasks has it's own executor with a corresponding thread pool, which may be more efficiently used if it was shared by using a single async executor across all processes.
of course, it requires to agree on one executor which doesn't sound realistic, but would it make sense in general?
But also, this is essentially what threads are... the main advantage of threads being, they coordinate raw assembly code, instead of something interpreted or JITted. OS schedulers are pretty well optimized by this point. The major overheads relate to maintaining security boundaries (e.g. swapping out the TLBs on context switch) and the cost of starting up a thread. Just having threads sitting around doesn't really cost anything.
Erlang gets away with this because of its programming model; it doesn't matter if it's interpreted or JITed or if you managed to compile it ahead of time. The relevant parts of the programming model are that any function call can yield, and because it's a functional language, there's no looping without function calls, so you can't stay busy forever without yielding.
The value of green thread over native threads is the difference in numbers. I haven't tried, but I imagine most OSes would fall over if you did a native thread per connection and you had a million connections; some OSes would probably hit a thread limit before then. Of course, if you've got a million connections, you're probably starting many per second, so startup cost is important (although maybe you could do a pool if it's cheaper to clean up an otherwise dying thread than kill it and start a new one)
Edit: read the article; that was a weird rant; I'm sure there are better things to rant about on an idle windows machine (like how much disk I/O they do... especially on spinning drives with Windows 10)
In Midori all programs were written in a special dialect of C#. Safety would have been ensured by the language, but all programs it actually ran were probably also written by very competent programmers (which doesn't happen in real life ...).
If I understand things correctly, the language would have imposed an early example of "Structured Concurrency" (although that term hadn't been invented yet)
<http://joeduffyblog.com/2015/11/19/asynchronous-everything/>
... no. Not if you rely on blocking system calls. Or wait for anything that isn't the CPU. Or run more than one application per CPU on your system. Or manage concurrent code flows at any level higher than source code.