Asynchrone Programmierung in C++: Von std::async bis zu Coroutinen
Asynchrone Programmierung in C++ ist ein leistungsstarkes Werkzeug zur Erstellung hochperformanter und reaktionsfähiger Anwendungen. In der modernen Welt, in der Mehrkernprozessoren zum Standard geworden sind, ist die Fähigkeit, parallele Aufgaben effektiv zu verwalten, eine unverzichtbare Fähigkeit für jeden C++-Entwickler. In diesem Artikel werden wir die wichtigsten Konzepte erläutern, angefangen bei den grundlegenden Mechanismen der Standardbibliothek bis hin zu modernen Coroutinen aus C++20.
Warum ist asynchrone Programmierung notwendig?
Das Hauptziel der asynchronen Programmierung ist es, den Hauptausführungsfaden des Programms nicht zu blockieren. Stellen Sie sich vor, Ihre Anwendung muss eine Datei aus dem Netzwerk laden, verarbeiten und dem Benutzer anzeigen. Wenn Sie diese Operationen synchron ausführen, würde die Oberfläche während des Ladevorgangs "einfrieren". Der asynchrone Ansatz ermöglicht es, eine langwierige Operation "im Hintergrund" zu starten, während der Hauptfaden weiterhin Benutzereingaben verarbeitet.
Grundlegende Ansätze zur Asynchronität in C++
In C++ gibt es mehrere Möglichkeiten, eine asynchrone Ausführung zu implementieren:
- Manuelle Thread-Verwaltung (std::thread) — ein low-level, aber flexibler Ansatz.
- std::async und std::future — ein high-level Mechanismus zum Starten asynchroner Aufgaben.
- std::packaged_task und std::promise — für eine feinere Kontrolle.
- Coroutinen (C++20) — eine moderne und elegante Methode zum Schreiben von asynchronem Code.
Verwendung von std::async und std::future
Dies ist der einfachste Weg, eine asynchrone Aufgabe zu starten. Die Funktion std::async gibt ein std::future-Objekt zurück, das das Ergebnis der Ausführung enthalten wird.
#include <iostream>#include <future>#include <thread>#include <chrono>
int heavy_computation(int x) { std::this_thread::sleep_for(std::chrono::seconds(2)); return x * x;}
int main() { std::cout << "Starte asynchrone Aufgabe..."; std::future<int> result = std::async(std::launch::async, heavy_computation, 42); // Mach etwas anderes, während die Aufgabe läuft std::cout << "Hauptfaden ist nicht blockiert!"; // Ergebnis abrufen (blockiert, wenn die Aufgabe noch nicht abgeschlossen ist) int value = result.get(); std::cout << "Ergebnis: " << value << ""; return 0;}Das Flag std::launch::async stellt sicher, dass die Aufgabe in einem separaten Thread ausgeführt wird. Wenn es nicht angegeben wird, kann die Implementierung die Ausführung bis zum Aufruf von .get() verzögern.
Arbeiten mit std::promise und std::packaged_task
Diese Mechanismen bieten mehr Kontrolle. std::promise ermöglicht es, einen Wert zu "versprechen", der später gesetzt wird, und std::packaged_task kapselt ein aufrufbares Objekt in einer Aufgabe.
#include <iostream>#include <future>#include <thread>
void set_value(std::promise<int>&& prom) { std::this_thread::sleep_for(std::chrono::seconds(1)); prom.set_value(100);}
int main() { std::promise<int> prom; std::future<int> fut = prom.get_future(); std::thread t(set_value, std::move(prom)); std::cout << "Warte auf Ergebnis..."; int value = fut.get(); std::cout << "Erhalten: " << value << ""; t.join(); return 0;}Wichtig: std::promise und std::future sind move-only Objekte. Sie können nicht kopiert, sondern nur verschoben werden.
Coroutinen in C++20: Eine neue Stufe der Asynchronität
Coroutinen sind Funktionen, die ihre Ausführung anhalten und später wieder aufnehmen können. Sie ermöglichen es, asynchronen Code so zu schreiben, als ob er synchron wäre.