Je suis assez familier avec le C ++ 11 de std::thread
, std::async
et des std::future
composants (voir par exemple cette réponse ), qui sont simple.
Cependant, je n'arrive pas à comprendre ce qui std::promise
est, ce qu'il fait et dans quelles situations il est le mieux utilisé. Le document standard lui-même ne contient pas beaucoup d'informations au-delà de son synopsis de classe, pas plus que :: thread .
Quelqu'un pourrait-il, s'il vous plaît, donner un exemple bref et succinct d'une situation où un std::promise
est nécessaire et où c'est la solution la plus idiomatique?
c++
multithreading
c++11
promise
standard-library
Kerrek SB
la source
la source
std::promise
oùstd::future
vient-elle?std::future
c'est ce qui vous permet de récupérer une valeur qui vous a été promise . Lorsque vous faites appelget()
à un futur, il attend le propriétaire dustd::promise
avec lequel il fixe la valeur (en faisant appelset_value
à la promesse). Si la promesse est détruite avant qu'une valeur ne soit définie, et que vous invoquez ensuiteget()
un avenir associé à cette promesse, vous obtiendrez unestd::broken_promise
exception parce qu'on vous a promis une valeur, mais il vous est impossible d'en obtenir une.std::broken_promise
est le meilleur identifiant nommé dans la bibliothèque standard. Et il n'y en a passtd::atomic_future
.Réponses:
Dans les mots de [futures.state], a
std::future
est un objet de retour asynchrone ("un objet qui lit les résultats d'un état partagé") et astd::promise
est un fournisseur asynchrone ("un objet qui fournit un résultat à un état partagé"), c'est-à-dire un la promesse est la chose sur laquelle vous définissez un résultat, afin que vous puissiez l' obtenir de l'avenir associé.Le fournisseur asynchrone est ce qui crée initialement l'état partagé auquel un futur fait référence.
std::promise
est un type de fournisseur asynchrone, enstd::packaged_task
est un autre, et le détail interne destd::async
est un autre. Chacun de ces éléments peut créer un état partagé et vous en donner unstd::future
qui partage cet état, et peut rendre l'état prêt.std::async
est un utilitaire de commodité de niveau supérieur qui vous donne un objet de résultat asynchrone et prend en charge en interne la création du fournisseur asynchrone et la préparation de l'état partagé à la fin de la tâche. Vous pouvez l'imiter avec unstd::packaged_task
(oustd::bind
et unstd::promise
) et unstd::thread
mais c'est plus sûr et plus facile à utiliserstd::async
.std::promise
est un peu inférieur, car lorsque vous souhaitez transmettre un résultat asynchrone à l'avenir, mais le code qui rend le résultat prêt ne peut pas être encapsulé dans une seule fonction adaptée à la transmissionstd::async
. Par exemple, vous pouvez avoir un tableau de plusieurspromise
s et s associésfuture
et avoir un seul thread qui effectue plusieurs calculs et définit un résultat sur chaque promesse.async
ne vous permettrait de renvoyer qu'un seul résultat, pour en renvoyer plusieurs, vous devriez en appelerasync
plusieurs fois, ce qui pourrait gaspiller des ressources.la source
future
est un exemple concret d'un objet de retour asynchrone , qui est un objet qui lit un résultat qui a été renvoyé de manière asynchrone, via l'état partagé. Apromise
est un exemple concret d'un fournisseur asynchrone , qui est un objet qui écrit une valeur dans l'état partagé, qui peut être lu de manière asynchrone. Je voulais ce que j'ai écrit.Je comprends un peu mieux la situation maintenant (en grande partie à cause des réponses ici!), Alors j'ai pensé ajouter une petite rédaction de ma part.
Il existe deux concepts distincts, bien que liés, dans C ++ 11: le calcul asynchrone (une fonction qui est appelée ailleurs) et l'exécution simultanée (un thread , quelque chose qui fonctionne simultanément). Les deux sont des concepts quelque peu orthogonaux. Le calcul asynchrone est juste une variante différente de l'appel de fonction, tandis qu'un thread est un contexte d'exécution. Les threads sont utiles en soi, mais pour les besoins de cette discussion, je les traiterai comme un détail d'implémentation.
Il existe une hiérarchie d'abstraction pour le calcul asynchrone. Par exemple, supposons que nous ayons une fonction qui accepte certains arguments:
Tout d'abord, nous avons le modèle
std::future<T>
, qui représente une valeur future de typeT
. La valeur peut être récupérée via la fonction membreget()
, qui synchronise efficacement le programme en attendant le résultat. Alternativement, un futur supportwait_for()
, qui peut être utilisé pour sonder si oui ou non le résultat est déjà disponible. Les contrats à terme devraient être considérés comme le remplacement asynchrone des types de retours ordinaires. Pour notre exemple de fonction, nous attendons astd::future<int>
.Maintenant, passons à la hiérarchie, du plus haut au plus bas:
std::async
: Le moyen le plus pratique et le plus simple d'effectuer un calcul asynchrone est via leasync
modèle de fonction, qui renvoie immédiatement le futur correspondant:Nous avons très peu de contrôle sur les détails. En particulier, nous ne savons même pas si la fonction est exécutée simultanément, en série
get()
ou par une autre magie noire. Cependant, le résultat est facilement obtenu en cas de besoin:Nous pouvons maintenant considérer comment implémenter quelque chose comme
async
, mais d'une manière que nous contrôlons. Par exemple, nous pouvons insister pour que la fonction soit exécutée dans un thread séparé. Nous savons déjà que nous pouvons fournir un thread séparé au moyen de lastd::thread
classe.Le niveau inférieur de l' abstraction fait exactement cela:
std::packaged_task
. Il s'agit d'un modèle qui encapsule une fonction et offre un avenir pour la valeur de retour des fonctions, mais l'objet lui-même est appelable, et l'appel est à la discrétion de l'utilisateur. Nous pouvons le configurer comme ceci:L'avenir devient prêt une fois que nous avons appelé la tâche et que l'appel est terminé. C'est le travail idéal pour un thread séparé. Nous devons juste nous assurer de déplacer la tâche dans le thread:
Le thread démarre immédiatement. Nous pouvons le
detach
faire, ou l'avoirjoin
à la fin de la portée, ou à tout moment (par exemple en utilisant lescoped_thread
wrapper d' Anthony Williams , qui devrait vraiment être dans la bibliothèque standard). Les détails d'utilisationstd::thread
ne nous concernent cependant pas ici; assurez-vous simplement de vous joindre ou de vous détacherthr
éventuellement. Ce qui importe, c'est que chaque fois que l'appel de fonction se termine, notre résultat est prêt:Maintenant, nous sommes au niveau le plus bas: comment pourrions-nous implémenter la tâche packagée? C'est là
std::promise
qu'intervient. La promesse est la pierre angulaire de la communication avec un avenir. Les principales étapes sont les suivantes:Le thread appelant fait une promesse.
Le thread appelant obtient un avenir de la promesse.
La promesse, ainsi que les arguments de fonction, sont déplacés dans un thread séparé.
Le nouveau thread exécute la fonction et remplit la promesse.
Le thread d'origine récupère le résultat.
À titre d'exemple, voici notre propre "tâche packagée":
L'utilisation de ce modèle est essentiellement la même que celle de
std::packaged_task
. Notez que le déplacement de la tâche entière résume le déplacement de la promesse. Dans des situations plus ad hoc, on pourrait également déplacer un objet de promesse explicitement dans le nouveau thread et en faire un argument de fonction de la fonction de thread, mais un wrapper de tâche comme celui ci-dessus semble être une solution plus flexible et moins intrusive.Faire des exceptions
Les promesses sont intimement liées aux exceptions. L'interface d'une promesse ne suffit pas à elle seule pour transmettre complètement son état, de sorte que des exceptions sont levées chaque fois qu'une opération sur une promesse n'a pas de sens. Toutes les exceptions sont de type
std::future_error
, qui dérive destd::logic_error
. Tout d'abord, une description de certaines contraintes:Une promesse construite par défaut est inactive. Les promesses inactives peuvent mourir sans conséquence.
Une promesse devient active lorsqu'un avenir est obtenu via
get_future()
. Cependant, un seul avenir peut être obtenu!Une promesse doit être satisfaite via
set_value()
ou avoir une exception définie viaset_exception()
avant la fin de sa durée de vie si son avenir doit être consommé. Une promesse satisfaite peut mourir sans conséquence etget()
devient disponible à l'avenir. Une promesse avec une exception lèvera l'exception stockée lors d'un appelget()
à l'avenir. Si la promesse ne meurt sans valeur ni exception, faire appelget()
à l'avenir entraînera une exception de "promesse rompue".Voici une petite série de tests pour démontrer ces différents comportements exceptionnels. Tout d'abord, le harnais:
Passons maintenant aux tests.
Cas 1: promesse inactive
Cas 2: promesse active, non utilisée
Cas 3: Trop de futurs
Cas 4: Promesse satisfaite
Cas 5: Trop de satisfaction
La même exception est levée s'il y a plus d'un ou l' autre de
set_value
ouset_exception
.Cas 6: exception
Cas 7: promesse non tenue
la source
std::function
a de nombreux constructeurs; aucune raison de ne pas les exposer au consommateur demy_task
.get()
l'avenir déclenche une exception. Je vais clarifier cela en ajoutant "avant qu'il ne soit détruit"; veuillez me faire savoir si cela est suffisamment clair.got()
monfuture
de grokking la bibliothèque de support de thread sur lepromise
de votre explication incroyable!Bartosz Milewski fournit une bonne rédaction.
std :: promise est l'une de ces parties.
...
Donc, si vous voulez utiliser un avenir, vous vous retrouvez avec une promesse que vous utilisez pour obtenir le résultat du traitement asynchrone.
Un exemple de la page est:
la source
Dans une approximation approximative, vous pouvez considérer
std::promise
comme l'autre extrémité d'unstd::future
(c'est faux , mais à titre d'illustration, vous pouvez penser comme si c'était le cas). L'extrémité consommateur du canal de communication utiliserait unstd::future
pour consommer les données de l'état partagé, tandis que le thread producteur utiliserait unstd::promise
pour écrire dans l'état partagé.la source
std::async
peut conceptuellement (ce n'est pas requis par la norme) compris comme une fonction qui crée unstd::promise
, le pousse dans un pool de threads (en quelque sorte, peut être un pool de threads, peut être un nouveau thread, ...) et renvoie l'associéstd::future
à l'appelant. Du côté client, vous attendriez sur lestd::future
et un thread à l'autre extrémité calculerait le résultat et le stockerait dans lestd::promise
. Remarque: la norme nécessite l' état partagé et lestd::future
mais pas l'existence d'unstd::promise
dans ce cas d'utilisation particulier.std::future
n'appellera pasjoin
sur le thread, il a un pointeur vers un état partagé qui est le tampon de communication réel. L' état partagé a un mécanisme de synchronisation (probablementstd::function
+std::condition_variable
pour verrouiller l'appelant jusqu'à ce que lestd::promise
soit rempli. L'exécution du thread est orthogonale à tout cela, et dans de nombreuses implémentations, vous pourriez trouver quistd::async
ne sont pas exécutées par de nouveaux threads qui sont ensuite joints, mais plutôt par un pool de threads dont la durée de vie s'étend jusqu'à la fin du programme.std::async
est que la bibliothèque d'exécution peut prendre les bonnes décisions pour vous en ce qui concerne le nombre de threads à créer et dans la plupart des cas, je m'attendrai à des runtimes qui utilisent des pools de threads. Actuellement, VS2012 utilise un pool de threads sous le capot, et il ne viole pas la règle comme si . Notez qu'il existe très peu de garanties qui doivent être remplies pour que ce particulier comme-si .std::promise
est le canal ou la voie pour les informations à renvoyer de la fonction asynchrone.std::future
est le mécanisme de synchronisation qui oblige l'appelant à attendre que la valeur de retour portée dans lestd::promise
soit prête (ce qui signifie que sa valeur est définie à l'intérieur de la fonction).la source
Il y a vraiment 3 entités principales dans le traitement asynchrone. C ++ 11 se concentre actuellement sur 2 d'entre eux.
Les éléments essentiels dont vous avez besoin pour exécuter une logique de manière asynchrone sont les suivants:
C ++ 11 appelle les choses dont je parle dans (1)
std::promise
et celles dans (3)std::future
.std::thread
est la seule chose prévue publiquement (2). Cela est regrettable car les vrais programmes doivent gérer les ressources de threads et de mémoire, et la plupart voudront que les tâches s'exécutent sur des pools de threads au lieu de créer et de détruire un thread pour chaque petite tâche (ce qui provoque presque toujours des hits de performances inutiles par lui-même et peut facilement créer des ressources famine qui est encore pire).Selon Herb Sutter et d'autres membres du C ++ 11 brain trust, il existe des plans provisoires pour en ajouter un
std::executor
- tout comme en Java - qui sera la base de pools de threads et de configurations logiquement similaires pour (2). Peut-être que nous le verrons dans C ++ 2014, mais mon pari ressemble plus à C ++ 17 (et Dieu nous aide s'ils bâclent la norme pour ceux-ci).la source
A
std::promise
est créé comme point de terminaison pour une paire promesse / future et lestd::future
(créé à partir de la promesse std :: à l'aide de laget_future()
méthode) est l'autre point de terminaison. Il s'agit d'une méthode simple et unique permettant de synchroniser deux threads, car un thread fournit des données à un autre thread via un message.Vous pouvez y penser comme un thread crée une promesse de fournir des données et l'autre thread recueille la promesse à l'avenir. Ce mécanisme ne peut être utilisé qu'une seule fois.
Le mécanisme de promesse / futur n'est qu'une direction, du thread qui utilise la
set_value()
méthode de astd::promise
au thread qui utilise leget()
de astd::future
pour recevoir les données. Une exception est générée si laget()
méthode d'un futur est appelée plusieurs fois.Si le thread avec le
std::promise
n'a pas utiliséset_value()
pour remplir sa promesse, alors lorsque le deuxième thread appelleget()
destd::future
pour collecter la promesse, le deuxième thread passera dans un état d'attente jusqu'à ce que la promesse soit remplie par le premier thread avec lestd::promise
lorsqu'il utilise laset_value()
méthode pour envoyer les données.Avec les coroutines proposées de la spécification technique N4663 Langages de programmation - Extensions C ++ pour Coroutines et la prise en charge du compilateur Visual Studio 2017 C ++
co_await
, il est également possible d'utiliserstd::future
etstd::async
d'écrire la fonctionnalité de coroutine. Voir la discussion et l'exemple dans https://stackoverflow.com/a/50753040/1466970 qui a une seule section qui discute de l'utilisation destd::future
avecco_await
.L'exemple de code suivant, une application de console Windows Visual Studio 2013 simple, montre l'utilisation de quelques-unes des classes / modèles de concurrence C ++ 11 et d'autres fonctionnalités. Il illustre une utilisation de promesse / avenir qui fonctionne bien, des threads autonomes qui effectueront certaines tâches et s'arrêteront, et une utilisation où un comportement plus synchrone est requis et en raison du besoin de notifications multiples, la paire promesse / futur ne fonctionne pas.
Une note à propos de cet exemple est les retards ajoutés à divers endroits. Ces délais ont été ajoutés uniquement pour s'assurer que les différents messages imprimés sur la console à l'aide
std::cout
seraient clairs et que le texte des différents threads ne serait pas mélangé.La première partie du
main()
crée trois threads supplémentaires et utilisestd::promise
etstd::future
pour envoyer des données entre les threads. Un point intéressant est où le thread principal démarre un thread, T2, qui attendra les données du thread principal, fera quelque chose, puis enverra des données au troisième thread, T3, qui fera alors quelque chose et renverra des données à la fil principal.La deuxième partie du
main()
crée deux threads et un ensemble de files d'attente pour autoriser plusieurs messages du thread principal à chacun des deux threads créés. On ne peut pas utiliserstd::promise
etstd::future
pour cela car le duo promesse / futur est un coup et ne peut pas être utilisé à plusieurs reprises.La source de la classe
Sync_queue
provient du langage de programmation C ++ de Stroustrup: 4e édition.Cette application simple crée la sortie suivante.
la source
La promesse est l'autre bout du fil.
Imaginez que vous ayez besoin de récupérer la valeur d'un
future
être calculé par unasync
. Cependant, vous ne voulez pas qu'il soit calculé dans le même thread, et vous ne générez même pas de thread "maintenant" - peut-être que votre logiciel a été conçu pour choisir un thread dans un pool, donc vous ne savez pas qui le fera. effectuer le calcul che à la fin.Maintenant, que passez-vous à ce thread / classe / entité (encore inconnu)? Vous ne passez pas le
future
, car c'est le résultat . Vous voulez passer quelque chose qui est connecté àfuture
et qui représente l'autre extrémité du fil , vous allez donc simplement interroger lefuture
sans savoir qui va réellement calculer / écrire quelque chose.C'est le
promise
. C'est une poignée reliée à votrefuture
. Si lefuture
est un haut - parleur et queget()
vous commencez à écouter jusqu'à ce qu'un son soit émis, lepromise
est un microphone ; mais pas n'importe quel microphone, c'est le microphone connecté avec un seul fil au haut-parleur que vous tenez. Vous savez peut-être qui est à l'autre bout, mais vous n'avez pas besoin de le savoir - vous le donnez simplement et attendez que l'autre partie dise quelque chose.la source
http://www.cplusplus.com/reference/future/promise/
Explication d'une phrase: furture :: get () attend pour toujours promse :: set_value ().
la source