C++20: More Details to Coroutines

Contents[Show]

After I gave you in my last post (C++20: Coroutines - A First Overview) the first impression of coroutines, I want to provide today more details. Once more, we get in C++20 not coroutines but a framework for building coroutines. 

TimelineCpp20

My job in this and further posts is to explain the framework for building coroutines. On the end, you can create your own or using 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 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?

Typical Use-Cases

Coroutines are the usual way to write event-driven applications. The event-driven application can be simulations, games, servers, user interfaces, or even algorithms. For example, I wrote a few years ago a simulator for a defibrillator 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 we have a scheduler that decides how long each task gets the CPU. Cooperative multitasking makes concurrency often easier because a concurrent job can not be interrupted in a critical region. If you are still puzzled with the terms cooperative and preemptive, I found an excellent overview and read here: Cooperative vs. Preemptive: a quest to maximize concurrency power

Underlying Ideas

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 you can use them as an argument to or 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
    on Linux.

Design Goals

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_return, co_yield, or co_await becomes a coroutine implicitly.

  • co_return: a coroutine uses co_return as return statement. 
  • co_yieldthanks 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 the three functions await_ready, await_suspend, and await_resume

Two Awaitables

 The C++20 standard already defines two awaitables as basic-building blocks: std::suspend_always, and std::suspend_never

  • std::suspend_always
struct suspend_always {
    constexpr bool await_ready() const noexcept { return false; }
    constexpr void await_suspend(coroutine_handle<>) const noexcept {}
    constexpr void await_resume() const noexcept {}
};

 

The awaitable std::suspend_always suspends always because of await_ready returns false. The opposite holds for std::suspend_never.

  • std::suspend_never
struct suspend_never {
    constexpr bool await_ready() const noexcept { return true; }
    constexpr void await_suspend(coroutine_handle<>) const noexcept {}
    constexpr void await_resume() const noexcept {}
};

 

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. 

Acceptor acceptor{443};               // (1)
 
while (true){
    Socket socket= acceptor.accept(); // blocking (2)
    auto request= socket.read();      // blocking (3)
    auto response= handleRequest(request);
    socket.write(response);           // blocking (4)
}

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, therefore, a resource sparing waiting server.

Acceptor acceptor{443};

while (true){
    Socket socket= co_await acceptor.accept();
    auto request= co_await socket.read();
    auto response= handleRequest(request);
    co_await socket.write(response);
}

You may guess. The crucial part to understand this coroutine are the awaitable expressions expr in the co_await expr calls. These expressions have to implement the functions await_ready, await_suspend, and await_resume

What's next?

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: Meeting C++, Matt Braun, Roman Postanciuc, Venkata Ramesh Gudpati, Tobias Zindl, Marko, G Prvulovic, Reinhold Dröge, Abernitzke, Richard Ohnemus, Frank Grimm, Sakib, Broeserl, António Pina, Markus Falkner, Darshan Mody, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Animus24, Jozo Leko, John Breland, espkk, and Wolfgang Gärtner.

 

Thanks in particular to:   crp4

 

   

Get your e-book at Leanpub:

The C++ Standard Library

 

Concurrency With Modern C++

 

Get Both as one Bundle

cover   ConcurrencyCoverFrame   bundle
With C++11, C++14, and C++17 we got a lot of new C++ libraries. In addition, the existing ones are greatly improved. The key idea of my book is to give you the necessary information to the current C++ libraries in about 200 pages. I also included more than 120 source files.  

C++11 is the first C++ standard that deals with concurrency. The story goes on with C++17 and will continue with C++20.

I'll give you a detailed insight into the current and upcoming concurrency in C++. This insight includes the theory and a lot of practice with more than 140 source files.

 

Get my books "The C++ Standard Library" (including C++17) and "Concurrency with Modern C++" in a bundle.

In sum, you get more than 700 pages full of modern C++ and more than 260 source files presenting concurrency in practice.

 

Get your interactive course

 

Modern C++ Concurrency in Practice

C++ Standard Library including C++14 & C++17

educative CLibrary

Based on my book "Concurrency with Modern C++" educative.io created an interactive course.

What's Inside?

  • 140 lessons
  • 110 code playgrounds => Runs in the browser
  • 78 code snippets
  • 55 illustrations

Based on my book "The C++ Standard Library" educative.io created an interactive course.

What's Inside?

  • 149 lessons
  • 111 code playgrounds => Runs in the browser
  • 164 code snippets
  • 25 illustrations

My Newest E-Books

Course: Modern C++ Concurrency in Practice

Course: C++ Standard Library including C++14 & C++17

Course: Embedded Programming with Modern C++

Course: Generic Programming (Templates)

Course: C++ Fundamentals for Professionals

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 3070

All 3929613

Currently are 184 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments