The main benefit is not having the cost of threads, f.ex. a default thread on Windows has something like 500k of stack iirc, scale that to 10000 concurrent requests and you're looking at 5gb of memory _just for stacks_, compared to a couroutine,etc that has perhaps a few kb's of usage (even if in deep stack-traces).
This is why we have stuff like io_uring these days, the bottleneck moved to the kernel calls (especially with Spectre style attacks) when concurrent costs in the applications went down.
In cooperative scheduling how is it possible to have two coroutines running concurrently?
It's M:N threading, most logical threads/tasks are more lightweight than full OS threads, the logical ones should preferably behave cooperatively but they can be scheduled onto any number of real worker threads (usually these systems picks something like 2x worker threads compared to real CPU/Core count to manage some codepaths not being as well behaved).
Erlang and .NET(with Task/async) uses this model, iirc modern Java does this also (but hides it) and I'd be surprised if the modern async Rust or C++ variations didn't do this as well.
If you know how to create asyncable types on .NET, the magic methods expected by C++ compiler will feel familiar.
It's funny that they're actually revamping the entire machinery for .NET 11 to defer allocations by default for non-waiting codepaths (Not sure, but iirc that machinery might require JIT compilation so would've perhaps not been possible to use for C++).
Then just don't move them between threads and you're good with thread locals, no?