Skip to content

Intro to C++ Coroutines: Concept

The time has come, fellow devs. We are on our way to uncover the newest concept of C++ language – Coroutines. They are already used by several programming languages, like

  • C# async tasks and yield iterables, forming LINQ base;
  • JS with awaitables, replacing the old way of making consecutive calls, that was hard to understand did a lot of code identation for cases that required a lot of async execution;
  • Python with synchronous generator
  • etc.

They were introduced in the recent C++20 standard. However, instead of handy classes such as Task<> and std::generator<> we received a complex and low-level toolkit to make our own promises and futures. Only C++23 gave us our first usable coroutine type (std::generator<>). It seems like the C++ committee followed the quote: “give a man a fish and you feed him for a day; teach a man to fish and you feed him for a lifetime”.

Today we will discuss what is needed to understand coroutines, and in the later chapters we will make our own little coroutine.

Theoretical Background

Wikipedia defines coroutines as follows:

Coroutines are computer components that allow execution to be suspended and resumed, generalizing subroutines for cooperative multitasking.

We need a lot more to understand them, though. To start off, let’s discuss what the first part of the definition above means. The part “to be suspended and resumed” feels like magic to a regular programmer, who has worked with regular functions their whole career, like me.

What does “suspend” mean?

It sounds a lot like system("pause"), or something like getchar(), that suspends execution, doesn’t it? However, although it may sound like ‘suspend the execution’ stops the program, that’s not even remotely close to what it means. In this context it means coroutines may switch between the execution of function bodies in real time, without program suspension.

Let’s draw a picture of that process: [source]

A calls B, but B resumes A… Still magic, nothing has changed.

Another Way

Let’s look at it another way. If the definition and implementation are arcane, we need to take ourselves to a journey of creativity. Behind every invention there is someone who wanted to make something work. What could that be exactly in this context? The name coroutine implies that some routine is running in parallel with another. However, there is a catch: there is no multithreading involved.

How can we achieve parallel execution with a single thread? Let’s take a look at video editing to illustrate how this works.

You could break several videos into chunks and splice these chunks together in a sequence that fits what you want to say. Or, looked at from another angle, you could program those videos to yield the timeline to other videos at the right time, resulting in one seamless timeline. What we see is only one timeline and several videos, resembling a single thread and tasks, that want to run on that thread.

One way to interpret that in the programming world would be a central function, that calls other functions in sequence. But the other approach is more interesting. You see, you can just stop one video, let the other video play for some time and continue the first video from the place where it had stopped previously. How do we achieve similar behavior in code? How do you suspend the code and resume after the suspend?

Example

Let’s assume we have a program, which should work like this:

void f() {
  int a = 0;
  ++a;
  ...suspend/yield magic()...
  print(a);
}

The idea that comes to mind is to just store a variable, that says where to execute the code. As my colleague once asked me, Is ‘switch on state’ a way to make a coroutine? It goes like this:

void Function(ExecutionState* state) {
  switch(*state){
  case 0:...
  case 1:...
  }
}

Except it is not yet complete. If there were any other variables inside or arguments, you would not get what you desire. Let’s make a simple example:

void Function(ExecutionState* state) {
  int a = 0;
  switch(*state){
  case 0: a++; *state = 1; return;
  case 1: printf("%d", a); return;
  }
}

What would the function print out if I was to call it twice? Well, the obvious answer would be 0. It will lose its internal state if called upon the second time and re-initialize the variable a with 0.

What do we know so far?

We have defined two parts that make a coroutine stand out:

  • The coroutine may yield, stopping its execution and letting other coroutines take the control.
  • The coroutine saves all the states of execution, including modified variables and input arguments.

If we look at the regular function, all its state is cleared when the execution goes out of scope. That is due to the nature of C functions, which save all the variables on a stack or in registers. What if it wasn’t constrained? Then we could use a heap or a static storage to save the state. The state would then retain its value.

Let’s make the example from above work:

struct ExecutionState{ int state; int a;};

void Function(ExecutionState* state) { //assume that state is initialized with zeroes
  switch(*state){
  case 0: state->a++; state->state = 1; return;
  case 1: printf("%d", state->a); return;
  }
}

Now that we placed the a variable in state we made the value retain its value across the calls.

The first one is harder, though, since the regular execution of functions only returns control to the callee.

Furthermore, we didn’t talk about corner cases of suspension of execution or exception handling.

A lot of tasks to solve indeed. We will tackle those in the next part of the series.

Conclusion

We have just scratched the surface of a big iceberg which is coroutines. For now, we have solved the persistent state problem, and we are on our way to define basic principles of a coroutine.

There is a lot more to unravel. We will explore what stackless and stackful coroutines are, symmetric and asymmetric ones and, of course, asynchronous usage of coroutines. For the time being, we have a basic understanding of the idea of coroutines and we are ready to make our first assumptions on how they work.

Thank you for reading, and see you in the next post.

 

Sincerely,

Ilya “Agrael” Doroshenko

About KDAB

If you like this article and want to read similar material, consider subscribing via our RSS feed.

Subscribe to KDAB TV for similar informative short video content.

KDAB provides market leading software consulting and development services and training in Qt, C++ and 3D/OpenGL. Contact us.

Categories: C++ / KDAB Blogs

1 thought on “Intro to C++ Coroutines: Concept”

  1. Hi Ilya,

    I really enjoyed the short intro to coroutines. I wanted to make a suggestion on the last code example. Where the code reads `switch(*state){`, I think it would make more sense to have `switch(state->state){` instead.

    I’m looking forward to reading the next post on coroutines!

Leave a Reply

Your email address will not be published. Required fields are marked *