Qu'est-ce que std :: promise?

384

Je suis assez familier avec le C ++ 11 de std::thread, std::asyncet des std::futurecomposants (voir par exemple cette réponse ), qui sont simple.

Cependant, je n'arrive pas à comprendre ce qui std::promiseest, 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::promiseest nécessaire et où c'est la solution la plus idiomatique?

Kerrek SB
la source
2
Voici du code avec: fr.cppreference.com/w/cpp/thread/future
chris
58
La version vraiment très courte est la suivante: d' std::promisestd::futurevient-elle? std::futurec'est ce qui vous permet de récupérer une valeur qui vous a été promise . Lorsque vous faites appel get()à un futur, il attend le propriétaire du std::promiseavec lequel il fixe la valeur (en faisant appel set_valueà la promesse). Si la promesse est détruite avant qu'une valeur ne soit définie, et que vous invoquez ensuite get()un avenir associé à cette promesse, vous obtiendrez une std::broken_promiseexception parce qu'on vous a promis une valeur, mais il vous est impossible d'en obtenir une.
James McNellis
14
Je recommande que, si vous pouvez / voulez, jetez un œil à C ++ Concurrency in Action par Anthony Williams
David Rodríguez - dribeas
32
@KerrekSB std::broken_promiseest le meilleur identifiant nommé dans la bibliothèque standard. Et il n'y en a pas std::atomic_future.
Cubbi
3
Downvoter, voulez-vous expliquer votre objection?
Kerrek SB

Réponses:

182

Dans les mots de [futures.state], a std::futureest un objet de retour asynchrone ("un objet qui lit les résultats d'un état partagé") et a std::promiseest 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::promiseest un type de fournisseur asynchrone, en std::packaged_taskest un autre, et le détail interne de std::asyncest un autre. Chacun de ces éléments peut créer un état partagé et vous en donner un std::futurequi partage cet état, et peut rendre l'état prêt.

std::asyncest 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 un std::packaged_task(ou std::bindet un std::promise) et un std::threadmais c'est plus sûr et plus facile à utiliser std::async.

std::promiseest 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 transmission std::async. Par exemple, vous pouvez avoir un tableau de plusieurs promises et s associés futureet avoir un seul thread qui effectue plusieurs calculs et définit un résultat sur chaque promesse. asyncne vous permettrait de renvoyer qu'un seul résultat, pour en renvoyer plusieurs, vous devriez en appeler asyncplusieurs fois, ce qui pourrait gaspiller des ressources.

Jonathan Wakely
la source
10
Pourrait gaspiller des ressources? Peut être incorrect si ce code ne peut pas être parallélisé.
Puppy
"retour asynchrone" et "lectures résultent d'un état partagé" sont pour la plupart orthogonales, ce qui rend la première phrase un peu déroutante. Voulez-vous dire que le partage de l'État se situe entre l'avenir et la promesse? Dans l'affirmative, veuillez le dire explicitement dès le départ.
einpoklum
@einpoklum pourquoi avez-vous arrêté de lire "objet de retour asynchrone" avant le dernier mot? Je cite la terminologie de la norme. A futureest 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é. A promiseest 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.
Jonathan Wakely
496

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:

int foo(double, char, bool);

Tout d'abord, nous avons le modèle std::future<T>, qui représente une valeur future de type T. La valeur peut être récupérée via la fonction membre get(), qui synchronise efficacement le programme en attendant le résultat. Alternativement, un futur support wait_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 a std::future<int>.

Maintenant, passons à la hiérarchie, du plus haut au plus bas:

  1. std::async: Le moyen le plus pratique et le plus simple d'effectuer un calcul asynchrone est via le asyncmodèle de fonction, qui renvoie immédiatement le futur correspondant:

    auto fut = std::async(foo, 1.5, 'x', false);  // is a std::future<int>

    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:

    auto res = fut.get();  // is an int
  2. 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 la std::threadclasse.

    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:

    std::packaged_task<int(double, char, bool)> tsk(foo);
    
    auto fut = tsk.get_future();    // is a std::future<int>

    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:

    std::thread thr(std::move(tsk), 1.5, 'x', false);

    Le thread démarre immédiatement. Nous pouvons le detachfaire, ou l'avoir joinà la fin de la portée, ou à tout moment (par exemple en utilisant le scoped_threadwrapper d' Anthony Williams , qui devrait vraiment être dans la bibliothèque standard). Les détails d'utilisation std::threadne nous concernent cependant pas ici; assurez-vous simplement de vous joindre ou de vous détacher thréventuellement. Ce qui importe, c'est que chaque fois que l'appel de fonction se termine, notre résultat est prêt:

    auto res = fut.get();  // as before
  3. Maintenant, nous sommes au niveau le plus bas: comment pourrions-nous implémenter la tâche packagée? C'est là std::promisequ'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":

    template <typename> class my_task;
    
    template <typename R, typename ...Args>
    class my_task<R(Args...)>
    {
        std::function<R(Args...)> fn;
        std::promise<R> pr;             // the promise of the result
    public:
        template <typename ...Ts>
        explicit my_task(Ts &&... ts) : fn(std::forward<Ts>(ts)...) { }
    
        template <typename ...Ts>
        void operator()(Ts &&... ts)
        {
            pr.set_value(fn(std::forward<Ts>(ts)...));  // fulfill the promise
        }
    
        std::future<R> get_future() { return pr.get_future(); }
    
        // disable copy, default move
    };

    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 de std::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 via set_exception()avant la fin de sa durée de vie si son avenir doit être consommé. Une promesse satisfaite peut mourir sans conséquence et get()devient disponible à l'avenir. Une promesse avec une exception lèvera l'exception stockée lors d'un appel get()à l'avenir. Si la promesse ne meurt sans valeur ni exception, faire appel get()à 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:

#include <iostream>
#include <future>
#include <exception>
#include <stdexcept>

int test();

int main()
{
    try
    {
        return test();
    }
    catch (std::future_error const & e)
    {
        std::cout << "Future error: " << e.what() << " / " << e.code() << std::endl;
    }
    catch (std::exception const & e)
    {
        std::cout << "Standard exception: " << e.what() << std::endl;
    }
    catch (...)
    {
        std::cout << "Unknown exception." << std::endl;
    }
}

Passons maintenant aux tests.

Cas 1: promesse inactive

int test()
{
    std::promise<int> pr;
    return 0;
}
// fine, no problems

Cas 2: promesse active, non utilisée

int test()
{
    std::promise<int> pr;
    auto fut = pr.get_future();
    return 0;
}
// fine, no problems; fut.get() would block indefinitely

Cas 3: Trop de futurs

int test()
{
    std::promise<int> pr;
    auto fut1 = pr.get_future();
    auto fut2 = pr.get_future();  //   Error: "Future already retrieved"
    return 0;
}

Cas 4: Promesse satisfaite

int test()
{
    std::promise<int> pr;
    auto fut = pr.get_future();

    {
        std::promise<int> pr2(std::move(pr));
        pr2.set_value(10);
    }

    return fut.get();
}
// Fine, returns "10".

Cas 5: Trop de satisfaction

int test()
{
    std::promise<int> pr;
    auto fut = pr.get_future();

    {
        std::promise<int> pr2(std::move(pr));
        pr2.set_value(10);
        pr2.set_value(10);  // Error: "Promise already satisfied"
    }

    return fut.get();
}

La même exception est levée s'il y a plus d'un ou l' autre de set_valueou set_exception.

Cas 6: exception

int test()
{
    std::promise<int> pr;
    auto fut = pr.get_future();

    {
        std::promise<int> pr2(std::move(pr));
        pr2.set_exception(std::make_exception_ptr(std::runtime_error("Booboo")));
    }

    return fut.get();
}
// throws the runtime_error exception

Cas 7: promesse non tenue

int test()
{
    std::promise<int> pr;
    auto fut = pr.get_future();

    {
        std::promise<int> pr2(std::move(pr));
    }   // Error: "broken promise"

    return fut.get();
}
Kerrek SB
la source
Vous avez dit "... qui synchronise efficacement le programme en attendant le résultat". . Que signifie «synchronise» ici? Que signifie toute la déclaration? Je n'arrive pas à comprendre cela. Aucun sens de «synchroniser» de cette entrée de dictionnaire ne m'aide à comprendre la phrase. Est-ce que "attendre" signifie "synchroniser"? Est-ce que chaque attente se synchronise? Je pense que je comprends partiellement ce que vous voulez dire, mais je ne suis pas sûr de ce que vous voulez réellement dire.
Nawaz
9
Belle réponse, merci pour votre aide.À propos de la partie de std :: async, je me souviens que nous pourrions déterminer qu'il engendrerait un autre thread ou fonctionnerait de manière synchrone avec flag (std :: launch :: async, std :: launch :: deferred)
StereoMatching
1
@FelixDombek: la transmission parfaite, etc., std::functiona de nombreux constructeurs; aucune raison de ne pas les exposer au consommateur de my_task.
Kerrek SB
1
@DaveedV .: Merci pour la rétroaction! Oui, c'est le cas de test 7: si vous détruisez la promesse sans définir de valeur ni d'exception, alors invoquer 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.
Kerrek SB
3
Enfin got()mon futurede grokking la bibliothèque de support de thread sur le promisede votre explication incroyable!
lune ensoleillée
33

Bartosz Milewski fournit une bonne rédaction.

C ++ divise l'implémentation des futures en un ensemble de petits blocs

std :: promise est l'une de ces parties.

Une promesse est un véhicule pour passer la valeur de retour (ou une exception) du thread exécutant une fonction au thread qui encaisse l'avenir de la fonction.

...

Un futur est l'objet de synchronisation construit autour de l'extrémité réceptrice du canal de promesse.

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:

promise<int> intPromise;
future<int> intFuture = intPromise.get_future();
std::thread t(asyncFun, std::move(intPromise));
// do some other stuff
int result = intFuture.get(); // may throw MyException
Paul Rubel
la source
4
Voir la promesse dans le constructeur du thread a finalement fait tomber le sou. L'article de Bartosz n'est peut-être pas le plus grand, mais il explique comment les éléments s'unissent. Merci.
Kerrek SB
28

Dans une approximation approximative, vous pouvez considérer std::promisecomme l'autre extrémité d'un std::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 un std::futurepour consommer les données de l'état partagé, tandis que le thread producteur utiliserait un std::promisepour écrire dans l'état partagé.

David Rodríguez - dribeas
la source
12
@KerrekSB: std::asyncpeut conceptuellement (ce n'est pas requis par la norme) compris comme une fonction qui crée un std::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 le std::futureet un thread à l'autre extrémité calculerait le résultat et le stockerait dans le std::promise. Remarque: la norme nécessite l' état partagé et le std::futuremais pas l'existence d'un std::promisedans ce cas d'utilisation particulier.
David Rodríguez - dribeas
6
@KerrekSB: std::futuren'appellera pas joinsur 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 (probablement std::function+ std::condition_variablepour verrouiller l'appelant jusqu'à ce que le std::promisesoit rempli. L'exécution du thread est orthogonale à tout cela, et dans de nombreuses implémentations, vous pourriez trouver qui std::asyncne 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.
David Rodríguez - dribeas
1
@ DavidRodríguez-dribeas: veuillez modifier les informations des commentaires dans votre réponse.
Marc Mutz - mmutz
2
@JonathanWakely: Cela ne signifie pas qu'il doit être exécuté dans un nouveau thread, mais seulement qu'il doit être exécuté de manière asynchrone comme s'il était exécuté dans un thread nouvellement créé. Le principal avantage std::asyncest 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 .
David Rodríguez - dribeas
1
Les sections locales de thread doivent être réinitialisées, mais la règle comme si tout autorise (c'est pourquoi je mets "comme si" en italique :)
Jonathan Wakely
11

std::promiseest le canal ou la voie pour les informations à renvoyer de la fonction asynchrone. std::futureest le mécanisme de synchronisation qui oblige l'appelant à attendre que la valeur de retour portée dans le std::promisesoit prête (ce qui signifie que sa valeur est définie à l'intérieur de la fonction).

kjp
la source
8

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:

  1. La tâche (logique empaquetée comme un objet foncteur) qui EXÉCUTERA «quelque part».
  2. Le nœud de traitement réel - un thread, un processus, etc. qui exécute ces foncteurs lorsqu'ils leur sont fournis. Examinez le modèle de conception "Command" pour une bonne idée de la façon dont un pool de threads de travail de base fait cela.
  3. Le descripteur de résultat : quelqu'un a besoin de ce résultat et a besoin d'un objet qui l'obtiendra pour lui. Pour la POO et d'autres raisons, toute attente ou synchronisation doit être effectuée dans les API de ce descripteur.

C ++ 11 appelle les choses dont je parle dans (1) std::promiseet celles dans (3) std::future. std::threadest 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).

Zack Yezek
la source
7

A std::promiseest créé comme point de terminaison pour une paire promesse / future et le std::future(créé à partir de la promesse std :: à l'aide de la get_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 a std::promiseau thread qui utilise le get()de a std::futurepour recevoir les données. Une exception est générée si la get()méthode d'un futur est appelée plusieurs fois.

Si le thread avec le std::promisen'a pas utilisé set_value()pour remplir sa promesse, alors lorsque le deuxième thread appelle get()de std::futurepour 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 le std::promiselorsqu'il utilise la set_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'utiliser std::futureet std::asyncd'é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 de std::futureavec co_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::coutseraient 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 utilise std::promiseet std::futurepour 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 utiliser std::promiseet std::futurepour cela car le duo promesse / futur est un coup et ne peut pas être utilisé à plusieurs reprises.

La source de la classe Sync_queueprovient du langage de programmation C ++ de Stroustrup: 4e édition.

// cpp_threads.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <iostream>
#include <thread>  // std::thread is defined here
#include <future>  // std::future and std::promise defined here

#include <list>    // std::list which we use to build a message queue on.

static std::atomic<int> kount(1);       // this variable is used to provide an identifier for each thread started.

//------------------------------------------------
// create a simple queue to let us send notifications to some of our threads.
// a future and promise are one shot type of notifications.
// we use Sync_queue<> to have a queue between a producer thread and a consumer thread.
// this code taken from chapter 42 section 42.3.4
//   The C++ Programming Language, 4th Edition by Bjarne Stroustrup
//   copyright 2014 by Pearson Education, Inc.
template<typename Ttype>
class Sync_queue {
public:
    void  put(const Ttype &val);
    void  get(Ttype &val);

private:
    std::mutex mtx;                   // mutex used to synchronize queue access
    std::condition_variable cond;     // used for notifications when things are added to queue
    std::list <Ttype> q;              // list that is used as a message queue
};

template<typename Ttype>
void Sync_queue<Ttype>::put(const Ttype &val) {
    std::lock_guard <std::mutex> lck(mtx);
    q.push_back(val);
    cond.notify_one();
}

template<typename Ttype>
void Sync_queue<Ttype>::get(Ttype &val) {
    std::unique_lock<std::mutex> lck(mtx);
    cond.wait(lck, [this]{return  !q.empty(); });
    val = q.front();
    q.pop_front();
}
//------------------------------------------------


// thread function that starts up and gets its identifier and then
// waits for a promise to be filled by some other thread.
void func(std::promise<int> &jj) {
    int myId = std::atomic_fetch_add(&kount, 1);   // get my identifier
    std::future<int> intFuture(jj.get_future());
    auto ll = intFuture.get();   // wait for the promise attached to the future
    std::cout << "  func " << myId << " future " << ll << std::endl;
}

// function takes a promise from one thread and creates a value to provide as a promise to another thread.
void func2(std::promise<int> &jj, std::promise<int>&pp) {
    int myId = std::atomic_fetch_add(&kount, 1);   // get my identifier
    std::future<int> intFuture(jj.get_future());
    auto ll = intFuture.get();     // wait for the promise attached to the future

    auto promiseValue = ll * 100;   // create the value to provide as promised to the next thread in the chain
    pp.set_value(promiseValue);
    std::cout << "  func2 " << myId << " promised " << promiseValue << " ll was " << ll << std::endl;
}

// thread function that starts up and waits for a series of notifications for work to do.
void func3(Sync_queue<int> &q, int iBegin, int iEnd, int *pInts) {
    int myId = std::atomic_fetch_add(&kount, 1);

    int ll;
    q.get(ll);    // wait on a notification and when we get it, processes it.
    while (ll > 0) {
        std::cout << "  func3 " << myId << " start loop base " << ll << " " << iBegin << " to " << iEnd << std::endl;
        for (int i = iBegin; i < iEnd; i++) {
            pInts[i] = ll + i;
        }
        q.get(ll);  // we finished this job so now wait for the next one.
    }
}

int _tmain(int argc, _TCHAR* argv[])
{
    std::chrono::milliseconds myDur(1000);

    // create our various promise and future objects which we are going to use to synchronise our threads
    // create our three threads which are going to do some simple things.
    std::cout << "MAIN #1 - create our threads." << std::endl;

    // thread T1 is going to wait on a promised int
    std::promise<int> intPromiseT1;
    std::thread t1(func, std::ref(intPromiseT1));

    // thread T2 is going to wait on a promised int and then provide a promised int to thread T3
    std::promise<int> intPromiseT2;
    std::promise<int> intPromiseT3;

    std::thread t2(func2, std::ref(intPromiseT2), std::ref(intPromiseT3));

    // thread T3 is going to wait on a promised int and then provide a promised int to thread Main
    std::promise<int> intPromiseMain;
    std::thread t3(func2, std::ref(intPromiseT3), std::ref(intPromiseMain));

    std::this_thread::sleep_for(myDur);
    std::cout << "MAIN #2 - provide the value for promise #1" << std::endl;
    intPromiseT1.set_value(22);

    std::this_thread::sleep_for(myDur);
    std::cout << "MAIN #2.2 - provide the value for promise #2" << std::endl;
    std::this_thread::sleep_for(myDur);
    intPromiseT2.set_value(1001);
    std::this_thread::sleep_for(myDur);
    std::cout << "MAIN #2.4 - set_value 1001 completed." << std::endl;

    std::future<int> intFutureMain(intPromiseMain.get_future());
    auto t3Promised = intFutureMain.get();
    std::cout << "MAIN #2.3 - intFutureMain.get() from T3. " << t3Promised << std::endl;

    t1.join();
    t2.join();
    t3.join();

    int iArray[100];

    Sync_queue<int> q1;    // notification queue for messages to thread t11
    Sync_queue<int> q2;    // notification queue for messages to thread t12

    std::thread t11(func3, std::ref(q1), 0, 5, iArray);     // start thread t11 with its queue and section of the array
    std::this_thread::sleep_for(myDur);
    std::thread t12(func3, std::ref(q2), 10, 15, iArray);   // start thread t12 with its queue and section of the array
    std::this_thread::sleep_for(myDur);

    // send a series of jobs to our threads by sending notification to each thread's queue.
    for (int i = 0; i < 5; i++) {
        std::cout << "MAIN #11 Loop to do array " << i << std::endl;
        std::this_thread::sleep_for(myDur);  // sleep a moment for I/O to complete
        q1.put(i + 100);
        std::this_thread::sleep_for(myDur);  // sleep a moment for I/O to complete
        q2.put(i + 1000);
        std::this_thread::sleep_for(myDur);  // sleep a moment for I/O to complete
    }

    // close down the job threads so that we can quit.
    q1.put(-1);    // indicate we are done with agreed upon out of range data value
    q2.put(-1);    // indicate we are done with agreed upon out of range data value

    t11.join();
    t12.join();
    return 0;
}

Cette application simple crée la sortie suivante.

MAIN #1 - create our threads.
MAIN #2 - provide the value for promise #1
  func 1 future 22
MAIN #2.2 - provide the value for promise #2
  func2 2 promised 100100 ll was 1001
  func2 3 promised 10010000 ll was 100100
MAIN #2.4 - set_value 1001 completed.
MAIN #2.3 - intFutureMain.get() from T3. 10010000
MAIN #11 Loop to do array 0
  func3 4 start loop base 100 0 to 5
  func3 5 start loop base 1000 10 to 15
MAIN #11 Loop to do array 1
  func3 4 start loop base 101 0 to 5
  func3 5 start loop base 1001 10 to 15
MAIN #11 Loop to do array 2
  func3 4 start loop base 102 0 to 5
  func3 5 start loop base 1002 10 to 15
MAIN #11 Loop to do array 3
  func3 4 start loop base 103 0 to 5
  func3 5 start loop base 1003 10 to 15
MAIN #11 Loop to do array 4
  func3 4 start loop base 104 0 to 5
  func3 5 start loop base 1004 10 to 15
Richard Chambers
la source
1

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 un async. 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é à futureet qui représente l'autre extrémité du fil , vous allez donc simplement interroger le futuresans savoir qui va réellement calculer / écrire quelque chose.

C'est le promise. C'est une poignée reliée à votre future. Si le futureest un haut - parleur et que get()vous commencez à écouter jusqu'à ce qu'un son soit émis, le promiseest 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.

Narcolessico
la source
0

http://www.cplusplus.com/reference/future/promise/

Explication d'une phrase: furture :: get () attend pour toujours promse :: set_value ().

void print_int(std::future<int>& fut) {
    int x = fut.get(); // future would wait prom.set_value forever
    std::cout << "value: " << x << '\n';
}

int main()
{
    std::promise<int> prom;                      // create promise

    std::future<int> fut = prom.get_future();    // engagement with future

    std::thread th1(print_int, std::ref(fut));  // send future to new thread

    prom.set_value(10);                         // fulfill promise
                                                 // (synchronizes with getting the future)
    th1.join();
    return 0;
}
Zhang
la source