Softwareentwicklung: C++20-Coroutinen effizient verwenden, Teil 1
Coroutinen sind ein mächtiges Feature in C++. Sie erlauben, asynchronen, nicht blockierenden Code zu schreiben.
- Andreas Fertig
Coroutinen findet man im Sprachstandard seit C++20. In der Informatik sind sie nichts Neues, der Begriff wurde erstmals 1958 von Melvin Conway geprägt. Um Coroutinen zu verstehen, ist es hilfreich, den Unterschied zwischen Funktionen und Coroutinen zu kennen. Beim Aufruf der Unterfunktion wird die Funktion unterbrochen, bis die Unterfunktion zurückkehrt. Jeder neue Aufruf der Unterfunktion beginnt bei null. Wer einen Zustand erhalten will, muss ihn über Parameter an die Unterfunktion übergeben.
Anders ist der Kontrollfluss bei einer Coroutine. Ein Aufrufer ruft eine Coroutine als Unterfunktion auf. Sie hat die Fähigkeit, sich zu unterbrechen und die Kontrolle an die aufrufende Funktion zurückzugeben. Das Abgeben der Kontrolle gelingt durch eines der drei neuen Schlüsselwörter in C++20: co_yield, co_await oder co_return. Bei co_return ist die Coroutine wie eine gewöhnliche Funktion beendet. Die beiden anderen Varianten ermöglichen es, die Kontrolle abzugeben und die Coroutine in dem Zustand wieder aufzunehmen, in dem sie verlassen wurde. Der Vorteil einer Coroutine ist also, dass sie sich pausieren und fortsetzen lässt. Die aufrufende Funktion bestimmt, ob eine Coroutine fortgesetzt wird oder ob das aktuelle Resultat bereits das gewünschte ist.
- Die in C++20 eingeführten Coroutinen sind eine bedeutende Erweiterung des Sprachstandards; sie ermöglichen es, asynchrone und nicht blockierende Operationen einfach zu implementieren.
- Coroutinen können die Ausführung unterbrechen und später wieder fortsetzen.
- Die Schlüsselwörter
co_yieldundco_awaitermöglichen es, Werte aus einer Coroutine zu erzeugen, ohne diese nach jeder Produktion zu beenden.
Gewöhnliche Funktionen sind ein Spezialfall einer Coroutine, die lediglich co_return nutzt und nicht co_yield oder co_await. Die Coroutine wendet einen simplen Trick an, um den Zustand zu speichern. Alle lokalen Daten und Parameter werden auf einem Heap-allozierten Speicher abgelegt. Jede Coroutine erstellt diesen Coroutinen-Frame beim erstmaligen Aufruf. Eine mehrfach gestartete Coroutine legt pro Start einen Frame an, der am Ende der Lebenszeit zerstört wird. Andere Programmiersprachen haben eine andere Implementierung: Dort speichern Coroutinen ihren Zustand in einem speziellen Stackbereich. Diese Form der Implementierung wurde für C++ diskutiert, aber letztlich verworfen. Das folgende Listing zeigt die Verwendung von co_yield und co_await; der vollständige Ablauf ist im Abschnitt "Die Beispiel-Coroutine Chat" beschrieben.