DEV Community

loading...
Cover image for Coroutines in C (2/3)

Coroutines in C (2/3)

Remo Dentato
I'm an old-time programmer for work and for fun. C is my favorite color. I'd love to share some thoughts on programming that go beyond the basics.
Updated on ・3 min read

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); 
    }
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
   }
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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); 
    }
Enter fullscreen mode Exit fullscreen mode

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.

Discussion (0)