A minimal implementation of coroutines in plain C. The original version was implemented and published by Tony Finch. His description of how it works is on his blog: Coroutines in less than 20 lines of standard C.
This fork exists mostly to add some documentation.
void *fun(void *arg)
{
printf("wim - %s\n",(char*)arg);
char *s = yield("teun");
printf("zus - %s - %s\n", (char*)arg, s);
s = yield("vuur");
printf("jet - %s - %s\n", (char*)arg, s);
return "gijs";
}
int main(void)
{
printf("make coroutine\n");
coro c = coroutine(fun);
printf("Resume, step 1\n");
char *s = resume(c, "aap");
printf("Resume, step 2 - %s\n", s);
s = resume(c, "noot");
printf("Resume, step 3 - %s\n", s);
if (resumable(c)) {
s = resume(c, "mies");
printf("Resume, step 4 - %s\n", s);
} else {
printf("completed\n");
}
if (resumable(c)) {
s = resume(c, "lam");
printf("Resume, step 5 - %s\n", s);
} else {
printf("completed\n");
}
return 0;
}
When the above program is run, the output is:
make coroutine
Resume, step 1
wim - aap
Resume, step 2 - teun
zus - aap - noot
Resume, step 3 - vuur
jet - aap - mies
Resume, step 4 - gijs
completed
- After making the coroutine, it is idle. It does not run until the first time it is resumed.
- When resuming a coroutine for the first time, the second parameter passed to
resume()
becomes the argument of the coroutine's function. Here, the callresume(c, "aap")
makesfun()
receiveaap
inarg
. - Any next time the main thread calls
resume()
, the parameter is passed as the return value ofyield()
. - The parameter passed to
yield()
is passed as the return value ofresume()
. - When a coroutine runs to completion, its return value is also passed as the return value of
resume()
(as if it were the final "yield", so to speak). - Function
yield()
passes execution back to the thread that created the coroutine. Callingyield()
when no coroutine has been set up, is ok (it simply does not yield). However, callingresume()
on a coroutine that has already run to completion causes an assert. The caller should check that a coroutine is resumable before resuming it.
When you start create coroutine, its stack starts below a spacer on the main stack. That means that the main thread's stack can grow for the size of the spacer before colliding with the coroutine's stack. The default size of this spacer is 16 KiB; it can be adjusted with constant COROUTINE_STACK
that is defined near the top of picoro.c
.
In case you are wondering about the test words "aap", "noot", "mies", etc. in the example, instead of the more ubiquitous "foo", "bar" & "baz", do an internet search for "leesplankje". Yes, that is how I learned to read.