After I gave you in my last post (C++20: Coroutines – A First Overview) my first impression of coroutines, I want to provide more details today. Once more, we get in C++20 not coroutines but a framework for building coroutines.
My job in this and further posts is to explain the framework for building coroutines. Ultimately, you can create your own or use an existing implementation of coroutines such as the excellent one cppcoro from Lewis Baker.
Today’s post is in-between: This post is not an overview but also not an in-depth dive into the coroutines framework that follows in the next posts.
The first question you may have is: When should I use coroutines?
Coroutines are the usual way to write event-driven applications. The event-driven application can be simulations, games, servers, user interfaces, or algorithms. For example, I wrote a a few years ago a defibrillator simulator in Python. This simulator helped us, in particular, to make clinical usability studies. A defibrillator is an event-driven application, and I implemented it, consequentially, based on the event-driven Python framework twisted.
Coroutines are also typically used for cooperative multitasking. The key to cooperative multitasking is that each task takes as much time as it needs. Cooperative multitasking stands in contrast to preemptive multitasking, for which a scheduler decides how long each task gets the CPU. Cooperative multitasking often makes concurrency easier because a concurrent job can not be interrupted in a critical region. If you are still puzzled by the terms cooperative and preemptive, I found an excellent overview and read here: Cooperative vs. Preemptive: a quest to maximize concurrency power
Coroutines in C++20 are asymmetric, first-class, and stackless.
- The workflow of an asymmetric coroutine goes back to the caller.
- First-class coroutines behave like data. Behaving like data means using them as an argument to return value from a function or store them in a variable.
- A stackless coroutine enables it to suspend and resume the top-level coroutine. The execution of the coroutine and the yielding from the coroutine comes back to the caller. In contrast, a stackful coroutine reserves a default stack for 1MB on Windows, and 2MB
Gor Nishanov, how was responsible for the standardization of coroutines in C++, described the design goals of coroutines. Coroutines should
- be highly scalable (to billions of concurrent coroutines).
- have highly efficient resume and suspend operations comparable in cost to the overhead of a function.
- seamlessly interact with existing facilities with no overhead.
- have open-ended coroutine machinery allowing library designers to develop coroutine libraries.
- exposing various high-level semantics such as generators, goroutines, tasks and more.
- usable in environments where exceptions are forbidden or not available.
Becoming a Coroutine
A function that uses the keywords
co_await becomes a coroutine implicitly.
- co_return: a coroutine uses co_return as return statement.
- co_yield: thanks to co_yield, you can implement a generator that generates an infinite data stream from which you can successively query values. The return type of the function call generatorForNumbers(int begin, int inc= 1), such as in the last post (C++20: Coroutines – A First Overview) is a generator. A generator internally holds a special promise pro so that a call co_yield i is equivalent to a call co_await pro.yield_value(i). Immediately after the call, the coroutine is paused.
- co_await:co_await eventually causes the execution of the coroutine to be suspended or resumed. The expression exp in co_await exp has to be a so-called awaitable expression. exp has to implement a specific interface. This interface consists of three functions await_ready, await_suspend, and await_resume.
The C++20 standard already defines two awaitables as basic-building blocks: std::suspend_always, and std::suspend_never.
The awaitable std::suspend_always suspends always because await_ready returns false. The opposite holds for std::suspend_never.
I hope a small example helps to make the theory easier to digest. A server is the hello world example for coroutines.
A Blocking and a Waiting Server
A server is an event-driven application. It typically waits in an event loop for an indication event of a client.
The following code snippet shows the structure of a straightforward server.
The sequential server answers each request in the same thread. It listens on port 443 (line 1), accepts its connections (line 2), reads the incoming data from the client (line 3), and writes its answer to the client (line 4). The calls in lines 2, 3, and 4 are blocking.
Thanks to co_await, the blocking calls can now be suspended and resumed. The resources-consuming blocking server becomes a resource-sparing waiting server.
You may guess. The crucial part of understanding this coroutine is the awaitable expressions expr in the co_await expr calls. These expressions have to implement the functions await_ready, await_suspend, and await_resume.
The framework for writing a coroutine consists of more than 20 functions that you partially have to implement and partially could overwrite. My next post dives deeper into this framework.
Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Matthieu Bolt, Stephen Kelley, Kyle Dean, Tusar Palauri, Dmitry Farberov, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, and Marco Parri Empoli.
Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.
|My special thanks to Embarcadero|
|My special thanks to PVS-Studio|
|My special thanks to Tipi.build|
|My special thanks to Take Up Code|
I’m happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.
- Embedded Programmierung mit modernem C++ 12.12.2023 – 14.12.2023 (Präsenzschulung, Termingarantie)
Standard Seminars (English/German)
Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.
- C++ – The Core Language
- C++ – The Standard Library
- C++ – Compact
- C++11 and C++14
- Concurrency with Modern C++
- Design Pattern and Architectural Pattern with C++
- Embedded Programming with Modern C++
- Generic Programming (Templates) with C++
- Clean Code with Modern C++
- Phone: +49 7472 917441
- Mobil:: +49 176 5506 5086
- Mail: schulung@ModernesCpp.de
- German Seminar Page: www.ModernesCpp.de
- Mentoring Page: www.ModernesCpp.org
Modernes C++ Mentoring,