Coroutines: Wierd Little Wonders

posted in Vaerydian
Published January 28, 2014
Advertisement

What is a Coroutine?


In order to understand them, you need to understand a bit of history. The term coroutine was coined back in the 60s by Melvin Conway whom is famous for Conway's Law, but we're not talking about that. Something you should have great familiarity with are routines and subroutines. Coroutines are special class of subroutine, specifically allowing multiple entry points.



So, what does multiple entry points mean?


Take a subroutines for example, when you call it, it executes it contents, then returns. If you call it exactly the same again, it will behave the same way again using.

so if you called:int subroutine(int a){ return a + 5;}
you would expect it always to increment 'a' by 5. Coroutines however are a different beast altogether especially since they have multiple entry points. The easiest way to demonstrate the difference between a coroutine is by looking at the below:int coroutine(int a){ return a + 5; return a + 4; return a + 3; return a + 2; return a + 1;}
in this case if you called the coroutine above, you would expect the following output:print coroutine(5); //10print coroutine(5); //9print coroutine(5); //8print coroutine(5); //7print coroutine(5); //6print coroutine(5); //???could be 6 or error or it starts over or other depending on implementation
That just looks like a state machine! tongue.png
Well, its not. What is not shown is that coroutines maintain their state in-between calls, they don't switch to another. So "local" variables called earlier in the coroutine are still available in the coroutine's context. like so:int coroutine(int a){ int b = a * 2; return a + b; b = a * 1.5; b = return a + b; return b - a;}
Here, you may noticed i did something weird. That 2nd to last line i wrote "b = return a + b". Depending on implementation, coroutines can allow you to do all sorts of cool things like that. Python 3.3 in particular has a very cool "yield from" ability that allows something like this. But the main point is that 'b' remains local to the coroutine and doesn't just disappear after the coroutine returns.

Pfff, so its just a weird iterator!
Yes, yes it is, in a weird way. Think of them more as an enhanced iterator in that coroutines can yield not only their operations, but yield to other coroutines, and those can yield to others, etc etc. Take a look at this great Python pseudocode example of two generators (a python iterator):def gen_a(a): while True: b = yield from gen_b(a) yield b + 3def gen_b(a): b = 0 while True: yield b if b > 100: return b b += a def main(): for i in gen_a(5): print(i)
In this example, we iterate over the output of gen_a forever. However, while its iterating, gen_a yields from gen_b until b is incremented over 100, ad which point it returns b and breaks out of its loop, allowing gen_a to then yield b + 3 at which point it begins again. This example shows two things, how to "terminate" a generator early, as well as how to yield results from another generator. However, thats not really the important thing, the important thing here is that coroutines are very similar to generators and iterators, in-fact, most languages build coroutines off these constructs. Addditionally, what is important is the concept of yielding behavior to another coroutine/iterator/generator, which demonstrates the 'co' part of 'coroutine'.

now we'll take this concept and do something different:def handle_request(): request = yield from get_request() #do some processing or whatever f = file(request.uri) data = yield from f.read() yield from response.send(data)def server(): while True: yield from handle_request()def main() serv_gen = server() while True: serv_gen.next()
So, in the above example, i create a forever loop calling server's "next" method, which is typically how you call a coroutine without using a "yield" or "yield from" keyword and causes a coroutine in python to re-enter and run to the next yield statement. In that case, it attempts to handle a request, which in turn attempts to get a request, yielding a result into request. Interesting aspect here is that this all happens over multiple calls to serv_gen.next()... This simple server can server some content (not really, its just pseudocode), but it takes multiple calls to do it. Think on that a second.

Ok, i thought about it... doesnt that make them take longer?


Perhaps it would take longer if i was just running 1 coroutine vs 1 subroutine as i am jumping in and out of the iteratiors/coroutines. But what if i wanted to run 2 or 3 or 8 or 100? all at the same time... do you see it now? do you see how coroutines start having a significant advantage over subroutines?

Because as wierd as they may be, they have a strong affinity for concurrent programming, especially when they can yield execution to another coroutine as shown above. Combined with worker threads or event loops, coroutines become extremely powerful. Allowing you to do a little work then yield to the next coroutine to do a little work, then again and again. Since coroutines are stateful, they make concurrent programming dead simple too as well as ensure things execute in-order in your program as well as avoid the dreaded pyramid code of callback-like models.



Ok, so now what?


well, this is just to get you thinking about coroutines. In further entries i'll be showing how to build a coroutine framework in c#, as well as demonstrate their use in ways such as building a simple HTTP server capable of serving 30,000+ requests/sec (static content). Eventually, we'll get around to showing how their constructs may be used to create the foundation of a concurrent game engine. But until then, grab python 3.3, the tulip library and play around or if you're curious, check out my in-work library over at github: AsyncCS
7 likes 0 comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement
Advertisement