Ich bin ziemlich vertraut mit C ++ 11 ist std::thread
, std::async
und std::future
Komponenten (siehe zB diese Antwort ), die geradlinig sind.
Ich kann jedoch nicht genau verstehen std::promise
, was es ist, was es tut und in welchen Situationen es am besten verwendet wird. Das Standarddokument selbst enthält nicht viele Informationen, die über die Klassensynopse hinausgehen, und auch nicht nur :: thread .
Könnte jemand bitte ein kurzes, prägnantes Beispiel für eine Situation geben, in der eine std::promise
benötigt wird und in der dies die idiomatischste Lösung ist?
c++
multithreading
c++11
promise
standard-library
Kerrek SB
quelle
quelle
std::promise
ist , wostd::future
s kommen.std::future
Auf diese Weise können Sie einen Wert abrufen, der Ihnen versprochen wurde . Wenn Sieget()
eine Zukunft anrufen , wartet sie, bis der Eigentümer der,std::promise
mit der sie den Wert festlegt (indem sieset_value
das Versprechen aufruft ). Wenn das Versprechen zerstört wird, bevor ein Wert festgelegt wird, und Sie dannget()
eine mit diesem Versprechen verbundene Zukunft aufrufen , erhalten Sie einestd::broken_promise
Ausnahme, weil Ihnen ein Wert versprochen wurde, aber es ist Ihnen unmöglich, einen zu erhalten.std::broken_promise
ist der am besten benannte Bezeichner in der Standardbibliothek. Und es gibt keinestd::atomic_future
.Antworten:
In den Worten von [futures.state]
std::future
ist a ein asynchrones Rückgabeobjekt ("ein Objekt, das Ergebnisse aus einem gemeinsam genutzten Status liest") und astd::promise
ist ein asynchroner Anbieter ("ein Objekt, das einem gemeinsam genutzten Status ein Ergebnis liefert"), dh a Versprechen ist es , was Sie setzen ein Ergebnis auf, so dass Sie bekommen es von der zugehörigen Zukunft.Der asynchrone Anbieter erstellt zunächst den gemeinsam genutzten Status, auf den sich eine Zukunft bezieht.
std::promise
ist eine Art von asynchronem Anbieter,std::packaged_task
ist eine andere und das interne Detail vonstd::async
ist eine andere. Jeder von ihnen kann einen gemeinsamen Status erstellen und Ihnen einen Status gebenstd::future
, der diesen Status teilt, und den Status bereitstellen.std::async
ist ein übergeordnetes Dienstprogramm, das Ihnen ein asynchrones Ergebnisobjekt zur Verfügung stellt und sich intern darum kümmert, den asynchronen Anbieter zu erstellen und den freigegebenen Status nach Abschluss der Aufgabe bereit zu stellen. Sie könnten es mit einemstd::packaged_task
(oderstd::bind
und einemstd::promise
) und einem emulieren,std::thread
aber es ist sicherer und einfacher zu verwendenstd::async
.std::promise
ist etwas niedriger, wenn Sie ein asynchrones Ergebnis an die Zukunft übergeben möchten, der Code, der das Ergebnis bereitstellt, jedoch nicht in einer einzigen Funktion zusammengefasst werden kann, die zum Übergeben an geeignet iststd::async
. Beispielsweise könnten Sie ein Array von mehrerenpromise
s und zugehörigenfuture
s haben und einen einzelnen Thread, der mehrere Berechnungen durchführt und für jedes Versprechen ein Ergebnis festlegt.async
Sie können nur ein einziges Ergebnis zurückgeben, um mehrere zurückzugeben, die Sieasync
mehrmals aufrufen müssen , wodurch möglicherweise Ressourcen verschwendet werden.quelle
future
ist ein konkretes Beispiel für ein asynchrones Rückgabeobjekt , bei dem es sich um ein Objekt handelt, das ein Ergebnis liest, das asynchron über den gemeinsam genutzten Status zurückgegeben wurde. Apromise
ist ein konkretes Beispiel für einen asynchronen Anbieter , bei dem es sich um ein Objekt handelt, das einen Wert in den gemeinsam genutzten Status schreibt, der asynchron gelesen werden kann. Ich meinte, was ich schrieb.Ich verstehe die Situation jetzt ein bisschen besser (nicht zuletzt aufgrund der Antworten hier!), Also dachte ich, ich füge selbst ein wenig hinzu.
In C ++ 11 gibt es zwei unterschiedliche, jedoch verwandte Konzepte: Asynchrone Berechnung (eine Funktion, die an einer anderen Stelle aufgerufen wird) und gleichzeitige Ausführung (ein Thread , der gleichzeitig funktioniert). Die beiden sind etwas orthogonale Konzepte. Asynchrone Berechnung ist nur eine andere Variante des Funktionsaufrufs, während ein Thread ein Ausführungskontext ist. Threads sind an sich nützlich, aber für den Zweck dieser Diskussion werde ich sie als Implementierungsdetail behandeln.
Es gibt eine Abstraktionshierarchie für die asynchrone Berechnung. Nehmen wir zum Beispiel an, wir haben eine Funktion, die einige Argumente akzeptiert:
Zunächst haben wir die Vorlage
std::future<T>
, die einen zukünftigen Wert des Typs darstelltT
. Der Wert kann über die Member-Funktion abgerufen werdenget()
, die das Programm effektiv synchronisiert, indem sie auf das Ergebnis wartet. Alternativ unterstützt eine Zukunftwait_for()
, mit der geprüft werden kann, ob das Ergebnis bereits verfügbar ist oder nicht. Futures sollten als asynchroner Drop-In-Ersatz für normale Renditetypen betrachtet werden. Für unsere Beispielfunktion erwarten wir astd::future<int>
.Nun zur Hierarchie von der höchsten zur niedrigsten Ebene:
std::async
: Die bequemste und einfachste Möglichkeit, eine asynchrone Berechnung durchzuführen, ist dieasync
Funktionsvorlage, die die passende Zukunft sofort zurückgibt:Wir haben sehr wenig Kontrolle über die Details. Insbesondere wissen wir nicht einmal, ob die Funktion gleichzeitig, seriell
get()
oder von einer anderen schwarzen Magie ausgeführt wird. Das Ergebnis ist jedoch bei Bedarf leicht zu erhalten:Wir können jetzt überlegen, wie wir so etwas implementieren sollen
async
, aber auf eine Weise, die wir kontrollieren. Zum Beispiel können wir darauf bestehen, dass die Funktion in einem separaten Thread ausgeführt wird. Wir wissen bereits, dass wir mit Hilfe derstd::thread
Klasse einen separaten Thread bereitstellen können .Die nächst niedrigere Abstraktionsebene macht genau das :
std::packaged_task
. Dies ist eine Vorlage, die eine Funktion umschließt und eine Zukunft für den Rückgabewert der Funktionen bietet. Das Objekt selbst kann jedoch aufgerufen werden, und der Aufruf liegt im Ermessen des Benutzers. Wir können es so einrichten:Die Zukunft wird bereit, sobald wir die Aufgabe aufrufen und der Anruf abgeschlossen ist. Dies ist der ideale Job für einen separaten Thread. Wir müssen nur sicherstellen, dass die Aufgabe in den Thread verschoben wird:
Der Thread wird sofort ausgeführt. Wir können es entweder
detach
oderjoin
am Ende des Bereichs haben oder wann immer (z. B. mit Anthony Williams 'scoped_thread
Wrapper, der eigentlich in der Standardbibliothek enthalten sein sollte). Die Details der Verwendungstd::thread
betreffen uns hier jedoch nicht; Stellen Sie einfach sicher, dass Sie sichthr
irgendwann anschließen oder trennen . Was zählt ist, dass unser Ergebnis immer dann bereit ist, wenn der Funktionsaufruf beendet ist:Jetzt sind wir auf der untersten Ebene: Wie würden wir die gepackte Aufgabe implementieren ? Hier kommt das ins
std::promise
Spiel. Das Versprechen ist der Baustein für die Kommunikation mit einer Zukunft. Die Hauptschritte sind folgende:Der aufrufende Thread macht ein Versprechen.
Der aufrufende Thread erhält aus dem Versprechen eine Zukunft.
Das Versprechen wird zusammen mit Funktionsargumenten in einen separaten Thread verschoben.
Der neue Thread führt die Funktion aus und erfüllt das Versprechen.
Der ursprüngliche Thread ruft das Ergebnis ab.
Als Beispiel ist hier unsere eigene "verpackte Aufgabe":
Die Verwendung dieser Vorlage entspricht im Wesentlichen der von
std::packaged_task
. Beachten Sie, dass das Verschieben der gesamten Aufgabe das Verschieben des Versprechens umfasst. In Ad-hoc-Situationen könnte man ein Versprechungsobjekt auch explizit in den neuen Thread verschieben und es zu einem Funktionsargument der Thread-Funktion machen, aber ein Task-Wrapper wie der obige scheint eine flexiblere und weniger aufdringliche Lösung zu sein.Ausnahmen machen
Versprechen sind eng mit Ausnahmen verbunden. Die Schnittstelle eines Versprechens allein reicht nicht aus, um seinen Zustand vollständig zu vermitteln. Daher werden Ausnahmen ausgelöst, wenn eine Operation an einem Versprechen keinen Sinn ergibt. Alle Ausnahmen sind vom Typ
std::future_error
, der sich von ableitetstd::logic_error
. Zunächst eine Beschreibung einiger Einschränkungen:Ein standardmäßig erstelltes Versprechen ist inaktiv. Inaktive Versprechen können ohne Konsequenz sterben.
Ein Versprechen wird aktiv, wenn eine Zukunft über erhalten wird
get_future()
. Es kann jedoch nur eine Zukunft erhalten werden!Ein Versprechen muss entweder erfüllt werden
set_value()
oder eine Ausnahme muss festgelegt werden,set_exception()
bevor seine Lebensdauer endet, wenn seine Zukunft konsumiert werden soll. Ein erfülltes Versprechen kann ohne Konsequenz sterben undget()
wird für die Zukunft verfügbar. Ein Versprechen mit einer Ausnahme löst die gespeicherte Ausnahme beim Aufruf vonget()
in der Zukunft aus. Wenn das Versprechen weder mit Wert noch mit Ausnahme stirbt, wird durch das Aufrufenget()
der Zukunft eine Ausnahme mit "gebrochenem Versprechen" ausgelöst.Hier sind einige kleine Testreihen, um diese verschiedenen außergewöhnlichen Verhaltensweisen zu demonstrieren. Erstens das Geschirr:
Nun zu den Tests.
Fall 1: Inaktives Versprechen
Fall 2: Aktives Versprechen, ungenutzt
Fall 3: Zu viele Futures
Fall 4: Erfülltes Versprechen
Fall 5: Zu viel Zufriedenheit
Die gleiche Ausnahme ausgelöst wird , wenn mehr als eine der ist entweder von
set_value
oderset_exception
.Fall 6: Ausnahme
Fall 7: Gebrochenes Versprechen
quelle
std::function
hat viele Konstruktoren; Kein Grund, diese nicht dem Verbraucher vonmy_task
.get()
eine Ausnahme festzulegen, löst der Aufruf der Zukunft eine Ausnahme aus. Ich werde dies klarstellen, indem ich "bevor es zerstört wird" hinzufüge; Bitte lassen Sie mich wissen, ob dies hinreichend klar ist.got()
habefuture
ich die Thread-Support-Bibliothek überpromise
Ihre erstaunliche Erklärung informiert !Bartosz Milewski liefert eine gute Zusammenfassung.
std :: Versprechen ist einer dieser Teile.
...
Wenn Sie also eine Zukunft nutzen möchten, erhalten Sie ein Versprechen, mit dem Sie das Ergebnis der asynchronen Verarbeitung erhalten.
Ein Beispiel von der Seite ist:
quelle
In grober Näherung können Sie
std::promise
als das andere Ende von a betrachtenstd::future
(dies ist falsch , aber zur Veranschaulichung können Sie denken, als ob es wäre). Das Consumer-Ende des Kommunikationskanals würde a verwendenstd::future
, um das Datum aus dem gemeinsam genutzten Status zu verbrauchen, während der Producer-Thread a verwenden würdestd::promise
, um in den gemeinsam genutzten Status zu schreiben.quelle
std::async
Kann konzeptionell (dies wird vom Standard nicht vorgeschrieben) als eine Funktion verstanden, die eine erstelltstd::promise
, diese in einen Thread-Pool schiebt (eine Art Thread-Pool, möglicherweise ein neuer Thread, ...) und zurückgibt diestd::future
dem Anrufer zugeordnete. Auf der Client-Seite würden Sie auf der wartenstd::future
und ein Thread am anderen Ende würde das Ergebnis berechnen und in der speichernstd::promise
. Hinweis: Der Standard erfordert den gemeinsamen Status undstd::future
das Vorhandensein einesstd::promise
in diesem speziellen Anwendungsfall, jedoch nicht das Vorhandensein eines .std::future
ruftjoin
den Thread nicht auf, er hat einen Zeiger auf einen gemeinsam genutzten Status, der der eigentliche Kommunikationspuffer ist. Der gemeinsam genutzte Status verfügt über einen Synchronisationsmechanismus (wahrscheinlichstd::function
+std::condition_variable
, um den Aufrufer zu sperren, bis derstd::promise
erfüllt ist. Die Ausführung des Threads ist orthogonal zu all dem, und in vielen Implementierungen werden Sie möglicherweise feststellen, dass diesestd::async
nicht von neuen Threads ausgeführt werden, die dann verbunden werden, aber eher durch einen Thread-Pool, dessen Lebensdauer sich bis zum Ende des Programms erstreckt.std::async
ist, dass die Laufzeitbibliothek die richtigen Entscheidungen für Sie in Bezug auf die Anzahl der zu erstellenden Threads treffen kann. In den meisten Fällen erwarte ich Laufzeiten, die Thread-Pools verwenden. Derzeit verwendet VS2012 einen Thread-Pool unter der Haube und verstößt nicht gegen die Als-ob- Regel. Beachten Sie, dass es sehr wenig Garantien sind , die für diese besondere erfüllt werden müssen , als-ob .std::promise
ist der Kanal oder Pfad für Informationen, die von der asynchronen Funktion zurückgegeben werden sollen.std::future
ist der Synchronisationsmechanismus, der den Anrufer warten lässt, bis der im Wert enthaltene Rückgabewertstd::promise
bereit ist (dh sein Wert wird innerhalb der Funktion festgelegt).quelle
Es gibt wirklich 3 Kernentitäten in der asynchronen Verarbeitung. C ++ 11 konzentriert sich derzeit auf zwei davon.
Die wichtigsten Dinge, die Sie benötigen, um eine Logik asynchron auszuführen, sind:
C ++ 11 nennt die Dinge, von denen ich in (1) spreche
std::promise
, und die in (3)std::future
.std::thread
ist das einzige, was öffentlich vorgesehen ist (2). Dies ist bedauerlich, da echte Programme Thread- und Speicherressourcen verwalten müssen und die meisten möchten, dass Aufgaben in Thread-Pools ausgeführt werden, anstatt für jede kleine Aufgabe einen Thread zu erstellen und zu zerstören (was fast immer zu unnötigen Leistungseinbußen führt und leicht Ressourcen erstellen kann Hunger, der noch schlimmer ist).Laut Herb Sutter und anderen
std::executor
Mitgliedern des C ++ 11 Brain Trust gibt es vorläufige Pläne, eine hinzuzufügen, die - ähnlich wie in Java - die Grundlage für Thread-Pools und logisch ähnliche Setups für (2) sein wird. Vielleicht werden wir es in C ++ 2014 sehen, aber meine Wette ähnelt eher C ++ 17 (und Gott helfe uns, wenn sie den Standard für diese verpfuschen).quelle
A
std::promise
wird als Endpunkt für ein Versprechen / Zukunftspaar erstellt und dasstd::future
(aus dem std :: Versprechen mit derget_future()
Methode erstellte) ist der andere Endpunkt. Dies ist eine einfache One-Shot-Methode, mit der zwei Threads synchronisiert werden können, wenn ein Thread über eine Nachricht Daten an einen anderen Thread liefert.Sie können sich vorstellen, dass ein Thread ein Versprechen zur Bereitstellung von Daten erstellt und der andere Thread das Versprechen für die Zukunft sammelt. Dieser Mechanismus kann nur einmal verwendet werden.
Der Versprechungs- / Zukunftsmechanismus ist nur eine Richtung, vom Thread, der die
set_value()
Methode von a verwendet,std::promise
bis zum Thread, der dieget()
von a verwendetstd::future
, um die Daten zu empfangen. Eine Ausnahme wird generiert, wenn dieget()
Methode einer Zukunft mehrmals aufgerufen wird.Wenn der Faden mit der
std::promise
nicht benutzt hatset_value()
sein Versprechen dann zu erfüllen , wenn die zweiten Thread Anrufeget()
von demstd::future
das Versprechen zu sammeln, wird der zweite Thread in einen Wartezustand gehen , bis das Versprechen durch den ersten Thread mit der erfüllt ist ,std::promise
wenn es die verwendeteset_value()
Methode um die Daten zu senden.Mit den vorgeschlagenen Coroutinen der technischen Spezifikation N4663 Programmiersprachen - C ++ - Erweiterungen für Coroutinen und der Unterstützung des Visual Studio 2017 C ++ - Compilers von
co_await
ist es auch möglich , Coroutinenfunktionen zu verwendenstd::future
undstd::async
zu schreiben. Weitere Informationen finden Sie in der Diskussion und im Beispiel unter https://stackoverflow.com/a/50753040/1466970. In diesem Abschnitt wird die Verwendung vonstd::future
with erläutertco_await
.Der folgende Beispielcode, eine einfache Visual Studio 2013 Windows-Konsolenanwendung, zeigt die Verwendung einiger C ++ 11-Parallelitätsklassen / -vorlagen und anderer Funktionen. Es zeigt eine Verwendung für Versprechen / Zukunft, die gut funktioniert, autonome Threads, die einige Aufgaben erledigen und anhalten, und eine Verwendung, bei der ein synchroneres Verhalten erforderlich ist und das Versprechen / Zukunftspaar aufgrund der Notwendigkeit mehrerer Benachrichtigungen nicht funktioniert.
Ein Hinweis zu diesem Beispiel sind die Verzögerungen, die an verschiedenen Stellen hinzugefügt wurden. Diese Verzögerungen wurden nur hinzugefügt, um sicherzustellen, dass die verschiedenen Nachrichten, die mit auf die Konsole gedruckt
std::cout
werden, klar sind und der Text aus den verschiedenen Threads nicht miteinander vermischt wird.Der erste Teil von
main()
besteht darin, drei zusätzliche Threads zu erstellenstd::promise
undstd::future
Daten zwischen den Threads zu verwenden und zu senden. Ein interessanter Punkt ist, wo der Hauptthread einen Thread, T2, startet, der auf Daten vom Hauptthread wartet, etwas tut und dann Daten an den dritten Thread, T3, sendet, der dann etwas tut und Daten zurück an den sendet Haupt-Bedroung.Der zweite Teil von
main()
erstellt zwei Threads und eine Reihe von Warteschlangen, um mehrere Nachrichten vom Hauptthread an jeden der beiden erstellten Threads zuzulassen. Wir können nicht verwendenstd::promise
undstd::future
dafür, weil das Versprechen / zukünftige Duo ein Schuss ist und nicht wiederholt verwendet werden kann.Die Quelle für die Klasse
Sync_queue
stammt aus Stroustrups The C ++ Programming Language: 4th Edition.Diese einfache Anwendung erstellt die folgende Ausgabe.
quelle
Das Versprechen ist das andere Ende des Drahtes.
Stellen Sie sich vor, Sie müssen den Wert eines von einem
future
berechneten Wesens abrufenasync
. Sie möchten jedoch nicht, dass es im selben Thread berechnet wird, und Sie erzeugen nicht einmal "jetzt" einen Thread - möglicherweise wurde Ihre Software so konzipiert, dass ein Thread aus einem Pool ausgewählt wird, sodass Sie nicht wissen, wer dies tun wird Führen Sie am Ende die Berechnung durch.Was übergeben Sie nun an diesen (noch unbekannten) Thread / Klasse / Entität? Sie bestehen das nicht
future
, da dies das Ergebnis ist . Sie möchten etwas übergeben, das mit dem verbunden istfuture
und das andere Ende des Drahtes darstellt , also fragen Sie einfach das ab,future
ohne zu wissen, wer tatsächlich etwas berechnet / schreibt.Das ist der
promise
. Es ist ein Griff, der mit Ihrem verbunden istfuture
. Wennfuture
es sich um einen Lautsprecher handelt undget()
Sie zuhören, bis ein Ton ertönt,promise
handelt es sich um ein Mikrofon . Aber nicht irgendein Mikrofon, sondern das Mikrofon, das mit einem einzigen Draht an den von Ihnen gehaltenen Lautsprecher angeschlossen ist. Sie wissen vielleicht, wer am anderen Ende ist, aber Sie müssen es nicht wissen - Sie geben es einfach und warten, bis die andere Partei etwas sagt.quelle
http://www.cplusplus.com/reference/future/promise/
Erklärung eines Satzes: furture :: get () wartet für immer auf promse :: set_value ().
quelle