Name at least one that cannot be solved better without them.
Name at least one that cannot be solved better without them.
So from there the benefits are around being able to use the sequential features of the language in combination (error handling, flow control, &c). Stackless transformations are a middle ground but there are plenty of things that become inconvenient without green thread stacks which make composition much easier.
No, it's not. See my other reply.
> there are plenty of things that become inconvenient without green thread stacks
There are no such things. Because you can have all of the sequential code you want, but without any of the downsides of threads.
I've spent plenty of time with both green threaded languages and non-green threaded languages. There is a most definitely a difference. A lot of the critiques you've given apply to event loops as well.
For example, data access, sure you can't write half a value but interleaving callbacks for work broken up into multiple stages is just as complex in terms of interleaved flows and would require the same careful treatments you would need in threaded code. The solution there is not to share data, even with events. I'm sure you implicitly do this yourself but there is nothing that makes either better or worse at it.
What you've failed to address are all of the trade-offs event loops have, like forgetful stack traces (and any effective fix keeps a stack of event flow contexts on the side, ironically).
In reality neither green threads or event loops are all that amazing on their own. Great runtimes need a whole lot more, so it seems kind of silly to sit on one side of the fence shouting that there are no downsides on your side of things because there obviously are plenty of problems worth solving better in both camps.
I think you didn't have a pleasure to work with event loops in languages with first-class functions, that's why you think they are on the same boat with green thread. They are not, of course. There are no trade-offs. You have multiple scopes to share memory from, one for each handler, that can execute only atomically, one for all handlers created by that function, that may execute out of order, and a global scope of course. You can compose functions, give them names and what not. You can even have a console, like Erlang does.
I've tried like a dozen of concurrency models, you cannot really convince me, that threads are any good for that.
Regarding green threads and state machines I would however add that while green threads allow to ditch some state machines (e.g. ones that wait until a complete packet was received) you won't get rid of all of them, e.g. you will still need state machines for lots of protocol implementations. E.g. if you are implementing the websocket protocol or HTTP/2, you have some interaction between incoming data, data that must be sent out and requests from the [library] user. The state of the connection depends on all of these, so you need some kind of connection state machine which would run most likely in it's own green thread (in addition to a sending thread and a receiving thread and a user thread). And now you have to implement clean synchronization between all of those threads. The lack of necessary synchronization (and the ability to observe a huge amount of state at once without danger) can make the implementation a little bit more easy in my experience. But here I also have to agree with strmpnk that a good design is necessary for both approaches, and accessing lots of shared mutable data in multiple callbacks is probably also not the cleanest approach.
completions create all sorts of concurrency problems because they are delivered to the program without any concurrency protection, and writing such protection correctly turns out to be non-trivial.
By delivering completions into coroutines, you gain all the concurrency protection that is offered by the green threads, without having to write it from scratch.
This is actually what the most basic event loop handles for you. OS signals, timers and asynchronous events are not delivered into the middle of your program, but into the point, where all of the event handlers are not running. That's the beauty of it. You can then react to the event and cancel whatever you are waiting to be running, or schedule something new.
With coroutines you can only safely handle those things each in a separate coroutine. But you don't need them isolated, you need your code to actually do something on those signals and timer events. So, you end up with a lot of flags spread all over other coroutines, protected by mutexes/channels. But then it's still not responsive, because you cannot unblock a currently blocking call, so many concurrency patterns do not apply and even bad things happen, like leaks and deadlocks. You rethink it again and move blocking calls each into a separate coroutine, so you would be able to construct a loop that waits for messages from other coroutines and reacts right away, abandoning blocking coroutines, if required. Hopefully you eventually realize, that you are emulating an event loop, but a very clumsy one, and go back to working with event handlers and higher-order functions.