20
Coroutines in C (2/3)
In the previous post I briefly described the concept of coroutines and pointed to some well established implementation in C.
Now is time to unleash some evil on the C world and illustrate my own, extremely simple, implementation that you can find on GitHub.
The evil is in the form of a Duff's device which takes advantage of the fact that C allows jumping in the middle of a loop.
I called this version of coroutines bees as I imagine them to fly around swarming and buzzing while doing their work but yelding one another when they enter the hive.
A common scenario in network programming is that servers create a worker for each request they receive and return to listen to other requests. Bees are meant to be a mechanism to implement those workers.
Let's use the iterator example to see how everything works:
#include "bee.h"
beedef(iter, int n;)
{
for (bee->n = 0; bee->n < 10; bee->n++) {
beeyeld;
}
beereturn;
}
int main(int argc, char *argv[])
{
iter counter = beenew(iter);
while (beefly(counter)) {
printf("%d\n",counter->n);
}
}
The implementation is all in the bee.h
file (~60 lines of code).
After including it, the first step is to define our bee:
beedef(iter, int n;)
{
... // code to be executed here.
beereturn;
}
The most notable points are:
-
iter
is the type of bee we are going to define. It will be a pointer type. -
int n;
this is the list of variables that we want to survive after yelding. -
beereturn
must always be present and no code can appear after it.
When a bee is activated with beefly()
it starts executing its code until it reaches beereturn
or beeyeld()
. In the first case the task is completed and the bee don't have any work to resume. In the second case, at the next activation, the work will start from the instruction immediately following the beeyeld()
function.
In this example, the execution will be suspended at each iteration of the for
loop.
for (bee->n = 0; bee->n < 10; bee->n++) {
beeyeld;
}
Note that you access the bee preserved variables through the bee
pointer (think of it as if it was self
or this
).
Let's see how the caller deals with bees. The first thing is to create a new bee:
iter counter = beenew(iter);
Unfortunately the type of the bee must be specified twice but, all in all, it seems pretty nice to me.
Now you make the bee fly until it has completed its task:
while (beefly(counter)) {
printf("%d\n",counter->n);
}
Whenever the bee yelds, beefly()
returns whether it can be resumed or not.
Note that you access the bee preserved variables using the pointer returned by beenew()
.
Pretty simple, right? Well, maybe too simple. In fact to use them you will need to add some timers, non blocking I/O, network communication, etc.
Also, the while(beefly(mybee))
idiom is a simple form of scheduler, you may need some more complex scheduling policy.
However, the fact that you may need something or may not need it it's exactly the reason why they are not included. Adding all sort of fancy stuff just because they may be useful or, worse, forcing a specific view on how these coroutines should be used, could limit their general usefulness and negate the beauty of their simplicity.
There will be time and place for developing complex stuff, but they don't belong to the concept of coroutines.
Now, there's only one step to complete my evil plan: explain how the code in bee.h
works. But this can be the subject for another post.
20