Generators, Iterators, and Coroutines
Python allows you to define a “generator” function where each call to the
generator yield
s a result. You can imagine this is great for things like
reading a buffer and appending to a file until the generator is empty.
It does this by creating a coroutine. In other words, when yield
is
encountered it saves the current state of the function and control is given back
to the calling function. The state is only restored when yield
is next called.
The state can be “done” which is when the generator is “empty.”
Coroutines are not threads because they share global variables and state with other coroutines. They are collaborative (as opposed to preemptive) so a coroutine cannot interrupt another coroutine while it’s running (whereas threads can, since they are preemptive).
Green threads are related to coroutines because they’re often implemented using coroutines (if your language supports them). Green threads are preemptive – they’re actually threads but implemented in userland (vs the kernel).
Fibers are like coroutines in that they are cooperative: a running fiber can’t be preempted / interrupted by another fiber. But in other senses they are like green threads. I guess one way to think about fibers is if you made green threads but neglected to write a scheduler and just said “OK all threads will run 1 at a time and never interrupt.”
Generators vs Iterators: C++
You might be curious if C++ had anything along the lines of Python’s generators. The answer is “no but you can emulate one.”
To do it in C++ you can use iterators. Your iterator can
do some work, manually save the state somewhere, provide a Next()
that reads
the state and decides how next to proceed, and indicate when it’s done
using a Done()
.
An example would be a buffered file reader. You read in some number of bytes,
save state (where in the file you are), and return how many bytes you read. When
Next()
is called, you read the next few lines or bytes. When the file is done,
you set state to done.