Since C is the implementation medium for many other languages coroutines did not get as much visibility as it should have.
I can only speculate why C left it out. Perhaps it was felt that it wont port too well outside of the PDP family. The other reason could be that coroutine as an abstraction completely leaves out how they ought to be scheduled. Perhaps they did not want to flesh out a schedule that would be portable everywhere. It is not a very satisfactory explanation because C wasnt that shy to leave things undefined. I think the only way to get an answer to this question is to go to the source, that would be Ken Thompson or Brian Kernighan.
Then, when UNIX and C exploded into far bigger things than they had probably ever dreamed of, it was too late. I think C would be a very different language if you had explicitly given Dennis Ritchie the task of writing the eternal lingua franca of programming with at least 80's-level computing resources at his disposal, but that's history for you.
Have you seen the plan9port implementation? It has the channel abstraction available as a library. The 'select' is called 'alt' in the code.
What I learned from the whole experience is that coroutines are a lot less mysterious than they look. Although I never had a chance to use them since then.
Code:
https://github.com/halayli/lthread
https://github.com/halayli/lthread_cpp
Docs:
Perhaps Rust could introduce this.
You don't need to save the stack if you have a stack per coroutine. Using madvise you can always let the kernel know of the memory you no longer need. So even if you allocate 1MB stack, it doesn't mean you are using 1MB of physical memory. If at some point you used 512kb then you yielded at 100kb stack usage, you can let the OS free up the difference.
In any case, I think it is best to just precompute the stack-size beforehand (assuming there is no recursion), and otherwise check for stack-overflow in recursive calls (and reallocate space if necessary). Pre-allocating megabytes of stack for each running coroutine sounds like madness, and a disaster waiting to happen (also, why should my stack overflow at 1M calls, when I have 64GB of main memory?)
On the other hand, the default stack size on linux is 8k, 1M is excessive and way more than you need in a sane program that doesn't have huge buffers on the stack.
Stack is not meant to use 64GB of memory, it's meant to be small to pass arguments and allocate small buffers on it. The rest of the 64GB is used for heap allocations.
Edit: He more recently wrote the Contiki operating system which is entirely based on protothreads: http://www.contiki-os.org/
1: https://github.com/contiki-os/contiki/blob/master/core/net/i...
P.S. UIP doesn't use the coroutine stuff at all it seems.
The uses of goto I saw compensate for C's lack of nested breaks and continues. Reasonable people may differ on this topic, but I'm perfectly fine with it. The real sin is obfuscating the natural control flow of a program by introducing dummy variables like try_again and keep_running, or being forced to split things into multiple functions just so you can use a return in lieu of a nested break. (There are single-exit zealots for whom an early-out return like that would be an equally grievous sin as the goto!)
In any case, my point was to demonstrate by example that serious software can be written this way. The point wasn't to hold up uIP or Contiki or anything else as impeccable exemplars of coding style.
> P.S. UIP doesn't use the coroutine stuff at all it seems.
It's not used in the core but in the protocol servers: https://github.com/adamdunkels/uip/blob/master/apps/webserve.... All the PT and PSOCK code is protothreads.
Personally I think the correct conclusion to draw is "C is not a language which supports coroutines" and not try to implement them at all.
EDIT: I agree really, C is not very suitable and that is the correct conclusion but if you are eg implementing a language that has them, assembly is the best solution.
What I realized is that a function's stack frame is little more than a struct living on the stack - once you have that it makes coroutines somewhat more straightforward to encapsulate. There are some oddities (e.g. the callee's frame is hoisted to the caller's frame, or you sometimes need the frame living on the heap).
I put some pseudo-c together as an end-goal for the toy compiler[1]. Thought this is an interesting approach, comments are appreciated.
[1]: https://gist.github.com/jcdickinson/af7fabf37c808d8e5814