Programmiersprache C++: cstd::execution: Asynchrone Algorithmen
Das Standard-C++-Framework std::execution bietet viele asynchrone Algorithmen fĂĽr diverse Aufgaben.

(Bild: SerbioVas/Shutterstock)
- Rainer Grimm
Nach der allgemeinen Vorstellung von std::execution
in meinem vorherigen Blogbeitrag widme ich mich den anpassbaren asynchronen Algorithmen.
Es ist nicht einfach, das Proposal P2300R10 vorzustellen. Erstens ist es leistungsstark und zweitens sehr lang. Daher konzentriere ich mich auf bestimmte Aspekte.
Die Prioritäten für das C++-Modell
Für das Proposal gilt eine Reihe von Prioritäten. Das C++-Modell für asynchronen Code soll
- zusammensetzbar und generisch sein, sodass Benutzer Code schreiben können, der mit vielen verschiedenen Arten von Ausführungsressourcen verwendet werden kann.
- gemeinsame asynchrone Muster in anpassbaren und wiederverwendbaren Algorithmen zusammenfassen, damit Benutzer nicht alles selbst erfinden mĂĽssen.
- es einfach machen, durch Konstruktion korrekt zu sein.
- die Vielfalt der Ausführungsressourcen und Ausführungsagenten unterstützen, da nicht alle Ausführungsagenten gleich sind; einige sind weniger leistungsfähig als andere, aber nicht weniger wichtig.
- ermöglichen, dass alles von einer Ausführungsressource angepasst werden kann, einschließlich der Übertragung an andere Ausführungsressourcen, aber nicht verlangen, dass Ausführungsressourcen alles anpassen –
dabei alle sinnvollen Anwendungsfälle, Domänen und Plattformen berücksichtigen. - dafür sorgen, dass Fehler weitergegeben werden, aber die Fehlerbehandlung keine Belastung darstellt.
- den Abbruch, der kein Fehler ist, unterstĂĽtzen.
- klare und präzise Antworten darauf haben, wo Dinge ausgeführt werden.
- in der Lage sein, die Lebensdauern von Objekten asynchron zu verwalten und zu beenden.
Die Begriffe Ausführungsressource, Ausführungsagent und Scheduler sind für das Verständnis von std::execution
unerlässlich. Hier sind die ersten vereinfachten Definitionen.
Eine Ausführungsressource ist eine Programmressourceneinheit, die eine Reihe von Ausführungsagenten verwaltet. Beispiele für Ausführungsressourcen sind der aktive Thread, ein Thread-Pool oder ein zusätzlicher Hardwarebeschleuniger.
Jeder Funktionsaufruf wird in einem AusfĂĽhrungsagenten ausgefĂĽhrt.
Ein Scheduler ist eine Abstraktion einer AusfĂĽhrungsressource mit einer einheitlichen, generischen Schnittstelle fĂĽr die Planung von Arbeit auf dieser Ressource. Er ist eine Fabrik fĂĽr Sender.
Hello World
Hier ist noch das „Hello World“ Program von std::execution
. Dieses Mal lässt sich das Programm im Compiler Explorer ausführen, und meine Analyse wird tiefer gehen.
// HelloWorldExecution.cpp
#include <exec/static_thread_pool.hpp>
#include <iostream>
#include <stdexec/execution.hpp>
int main() {
exec::static_thread_pool pool(8);
auto sch = pool.get_scheduler();
auto begin = stdexec::schedule(sch);
auto hi = stdexec::then(begin, [] {
std::cout << "Hello world! Have an int.\n";
return 13;
});
auto add_42 = stdexec::then(hi, [](int arg) { return arg + 42; });
auto [i] = stdexec::sync_wait(add_42).value();
std::cout << "i = " << i << '\n';
}
Zunächst einmal ist hier die Ausgabe des Programms:
Das Programm beginnt mit der Einbindung der erforderlichen Header: <exec/static_thread_pool.hpp
> fĂĽr die Erstellung eines Thread-Pools und <stdexec/execution.hpp
> fĂĽr ausfĂĽhrungsbezogene Utilities.
In der main
Funktion wird ein static_thread_pool pool
mit acht Threads erstellt.
Die Memberfunktion get_scheduler
des Thread-Pools wird aufgerufen, um ein leichtgewichtiges Handle fĂĽr das AusfĂĽhrungsressourcen-Scheduler namens sch
zu erhalten, das zur Planung von Sendern im Thread-Pool verwendet wird. In diesem Fall ist die Ausführungsressource ein Thread-Pool, es könnte sich aber auch um den Haupt-Thread, die GPU oder ein Task-Framework handeln
Das Programm erstellt dann eine Reihe von Sendern, die von AusfĂĽhrungsagenten ausgefĂĽhrt werden.
Der erste Sender begin
wird mit der Funktion stdexec::schedule
erstellt, die einen Sender auf dem angegebenen Scheduler sch
plant. stdexec::schedule
ist eine sogenannte Senderfabrik. Es gibt noch weitere Senderfabriken. Ich verwende den Namensraum des kommenden Standards:
Der nächste Sender hi
verwendet den Senderadapter stdexec::then
, der den Sender begin
und eine Lambda-Funktion verwendet. Diese Lambda-Funktion gibt „Hello world! Have an int
.“ an die Konsole aus und gibt den Integer-Wert 13
zurĂĽck. Der dritte Sender add_42
wird ebenfalls mit dem Sender-Adapter stdexec::then
erstellt. Die Fortsetzung ĂĽbernimmt die hi
Task und ein weiteres Lambda, das ein Integer-Argument arg
entgegennimmt und das Ergebnis der Addition von 42
zurĂĽckgibt. Sender werden asynchron ausgefĂĽhrt und sind im Allgemeinen zusammensetzbar.
std::execution
bietet weitere Senderadapter:
execution::continues_on
execution::then
execution::upon_*
execution::let_*
execution::starts_on
execution::into_variant
execution::stopped_as_optional
execution::stopped_as_error
execution::bulk
execution::split
execution::when_all
Im Gegensatz zu den Sendern wird der Sender-Consumer synchron ausgefĂĽhrt. Der stdexec::sync_wait-
Aufruf wartet auf die Fertigstellung des add_42-
Senders. Die value
-Methode wird fĂĽr das Ergebnis von sync_wait
aufgerufen, um den vom Sender erzeugten Wert zu erhalten, der in die Variable i
entpackt wird.
this_thread::sync_wait
ist der einzige Sender-Consumer im Execution-Framework.
Wie geht es weiter?
In meinem nächsten Artikel werde ich einen anspruchsvolleren Algorithmus analysieren. (rme)