Che cos'è std :: promise?


384

Sono abbastanza familiarità con C ++ 11 di std::thread, std::asynce std::futurecomponenti (ad esempio, vedere questa risposta ), che sono straight-forward.

Tuttavia, non riesco a capire cosa std::promisesia, cosa faccia e in quali situazioni sia meglio utilizzato. Lo stesso documento standard non contiene molte informazioni oltre alla sua sinossi di classe, e nemmeno :: thread .

Qualcuno potrebbe per favore dare un breve, breve esempio di una situazione in cui std::promiseè necessario e dove è la soluzione più idiomatica?


2
Ecco un po 'di codice con esso in: en.cppreference.com/w/cpp/thread/future
chris

58
La versione davvero molto breve è: std::promiseè da dove std::futureviene. std::futureè ciò che ti consente di recuperare un valore che ti è stato promesso . Quando si chiama get()un futuro, attende fino al proprietario del std::promisecon cui imposta il valore (chiamando set_valuela promessa). Se la promessa viene distrutta prima che sia impostato un valore e quindi invochi get()un futuro associato a quella promessa, otterrai std::broken_promiseun'eccezione perché ti è stato promesso un valore, ma è impossibile per te ottenerne uno.
James McNellis,

14
Raccomando che, se puoi / vuoi, dai un'occhiata a C ++ Concurrency in Action di Anthony Williams
David Rodríguez - dribeas,

32
@KerrekSB std::broken_promiseè l'identificatore con il nome migliore nella libreria standard. E non c'è std::atomic_future.
Cubbi,

3
Downvoter, ti interessa spiegare la tua obiezione?
Kerrek SB,

Risposte:


182

Nelle parole di [futures.state] a std::futureè un oggetto di ritorno asincrono ("un oggetto che legge i risultati da uno stato condiviso") e a std::promiseè un fornitore asincrono ("un oggetto che fornisce un risultato a uno stato condiviso") cioè un promessa è la cosa su cui si imposta un risultato, in modo da poterlo ottenere dal futuro associato.

Il provider asincrono è ciò che inizialmente crea lo stato condiviso a cui fa riferimento un futuro. std::promiseè un tipo di provider asincrono, std::packaged_taskè un altro e il dettaglio interno di std::asyncè un altro. Ognuno di questi può creare uno stato condiviso e darti uno std::futureche condivide tale stato e può renderlo pronto.

std::asyncè un'utilità di livello superiore che offre un oggetto risultato asincrono e si occupa internamente della creazione del provider asincrono e della preparazione dello stato condiviso al termine dell'attività. Potresti emularlo con un std::packaged_task(oe std::binda std::promise) e un std::threadma è più sicuro e più facile da usare std::async.

std::promiseè un po 'di livello inferiore, perché quando si desidera passare un risultato asincrono al futuro, ma il codice che rende pronto il risultato non può essere racchiuso in un'unica funzione adatta al passaggio std::async. Ad esempio, potresti avere un array di più se promiseassociati futuree avere un singolo thread che esegue diversi calcoli e imposta un risultato su ogni promessa. asyncti consentirebbe solo di restituire un singolo risultato, per restituire più devi chiamare asyncpiù volte, il che potrebbe sprecare risorse.


10
Potrebbero sprecare risorse? Potrebbe essere errato, se quel codice non può essere parallelizzato.
Cucciolo

"ritorno asincrono" e "legge il risultato dallo stato condiviso" sono per lo più ortogonali, il che rende la prima frase un po 'confusa. Intendi dire che la condivisione dello stato è tra il futuro e la promessa? In tal caso, si prega di dirlo esplicitamente fin dall'inizio.
einpoklum,

@einpoklum perché hai smesso di leggere "oggetto di ritorno asincrono" prima dell'ultima parola? Sto citando la terminologia dello standard. A futureè un esempio concreto di un oggetto di ritorno asincrono , che è un oggetto che legge un risultato che è stato restituito in modo asincrono, tramite lo stato condiviso. A promiseè un esempio concreto di un provider asincrono , che è un oggetto che scrive un valore nello stato condiviso, che può essere letto in modo asincrono. Intendevo quello che ho scritto.
Jonathan Wakely,

496

Capisco la situazione un po 'meglio ora (non poco per via delle risposte qui!), Quindi ho pensato di aggiungere un piccolo commento personale.


Esistono due concetti distinti, sebbene correlati, in C ++ 11: il calcolo asincrono (una funzione che viene chiamata altrove) e l'esecuzione simultanea (un thread , qualcosa che funziona contemporaneamente). I due sono concetti in qualche modo ortogonali. Il calcolo asincrono è solo un diverso sapore della chiamata di funzione, mentre un thread è un contesto di esecuzione. I thread sono utili a pieno titolo, ma ai fini di questa discussione, li tratterò come un dettaglio di implementazione.


Esiste una gerarchia di astrazione per il calcolo asincrono. Per esempio, supponiamo di avere una funzione che accetta alcuni argomenti:

int foo(double, char, bool);

Prima di tutto, abbiamo il modello std::future<T> , che rappresenta un valore futuro di tipo T. Il valore può essere recuperato tramite la funzione membro get(), che sincronizza efficacemente il programma in attesa del risultato. In alternativa, un futuro supporta wait_for(), che può essere utilizzato per sondare se il risultato è già disponibile. I futures dovrebbero essere considerati la sostituzione del drop-in asincrono per i tipi di rendimento ordinari. Per la nostra funzione di esempio, ci aspettiamo a std::future<int>.

Ora, nella gerarchia, dal livello più alto al più basso:

  1. std::async: Il modo più conveniente e diretto per eseguire un calcolo asincrono è tramite async modello di funzione, che restituisce immediatamente il futuro corrispondente:

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

    Abbiamo pochissimo controllo sui dettagli. In particolare, non sappiamo nemmeno se la funzione viene eseguita contemporaneamente, in serieget() o da qualche altra magia nera. Tuttavia, il risultato è facilmente ottenibile quando necessario:

    auto res = fut.get();  // is an int
  2. Ora possiamo considerare come implementare qualcosa del genere async, ma in un modo che noi controllare. Ad esempio, possiamo insistere sul fatto che la funzione sia eseguita in un thread separato. Sappiamo già che possiamo fornire un thread separato tramite la std::threadclasse.

    Il prossimo livello inferiore di astrazione fa esattamente questo: std::packaged_task . Questo è un modello che avvolge una funzione e fornisce un futuro per il valore restituito dalle funzioni, ma l'oggetto stesso è richiamabile e chiamarlo è a discrezione dell'utente. Possiamo configurarlo in questo modo:

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

    Il futuro diventa pronto quando chiamiamo l'attività e la chiamata termina. Questo è il lavoro ideale per un thread separato. Dobbiamo solo assicurarcelo spostare l'attività nel thread:

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

    Il thread inizia a funzionare immediatamente. Possiamo detachfarlo o averlo joinalla fine dell'ambito o quando (ad es. Utilizzando il scoped_threadwrapper di Anthony Williams , che dovrebbe essere nella libreria standard). I dettagli dell'uso std::threadnon ci riguardano qui, però; assicurati di unirti o staccarti thralla fine. Ciò che conta è che ogni volta che termina la chiamata di funzione, il nostro risultato è pronto:

    auto res = fut.get();  // as before
  3. Ora siamo al livello più basso: come implementeremo l'attività in pacchetto? È qui che std::promiseentra in gioco. La promessa è la base per comunicare con un futuro. I passaggi principali sono questi:

    • Il thread chiamante fa una promessa.

    • Il thread chiamante ottiene un futuro dalla promessa.

    • La promessa, insieme agli argomenti della funzione, vengono spostati in un thread separato.

    • Il nuovo thread esegue la funzione e mantiene la promessa.

    • Il thread originale recupera il risultato.

    A titolo di esempio, ecco il nostro "task impacchettato":

    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'uso di questo modello è essenzialmente lo stesso di quello di std::packaged_task. Si noti che lo spostamento dell'intera attività comporta lo spostamento della promessa. In situazioni più ad hoc, si potrebbe anche spostare esplicitamente un oggetto promessa nel nuovo thread e renderlo un argomento di funzione della funzione thread, ma un wrapper di attività come quello sopra sembra una soluzione più flessibile e meno invadente.


Fare eccezioni

Le promesse sono intimamente correlate alle eccezioni. L'interfaccia di una promessa da sola non è sufficiente per comunicare completamente il suo stato, quindi vengono generate eccezioni ogni volta che un'operazione su una promessa non ha senso. Tutte le eccezioni sono di tipo std::future_error, da cui deriva std::logic_error. Prima di tutto, una descrizione di alcuni vincoli:

  • Una promessa costruita per impostazione predefinita è inattiva. Le promesse inattive possono morire senza conseguenze.

  • Una promessa diventa attiva quando si ottiene un futuro tramite get_future(). Tuttavia, è possibile ottenere solo un futuro!

  • Una promessa deve essere soddisfatta tramite set_value()o avere un'eccezione impostata set_exception()prima della fine della sua vita se si vuole consumare il suo futuro. Una promessa soddisfatta può morire senza conseguenze e get()diventa disponibile in futuro. Una promessa con un'eccezione solleverà l'eccezione memorizzata su chiamata del get()futuro. Se la promessa muore senza valore né eccezione, la chiamata get()al futuro solleverà un'eccezione "promessa non mantenuta".

Ecco una piccola serie di test per dimostrare questi vari comportamenti eccezionali. Innanzitutto, l'imbracatura:

#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;
    }
}

Ora ai test.

Caso 1: promessa inattiva

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

Caso 2: promessa attiva, inutilizzata

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

Caso 3: troppi futuri

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

Caso 4: promessa soddisfatta

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".

Caso 5: troppa soddisfazione

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 stessa eccezione viene generata se c'è più di uno di uno di set_valueo set_exception.

Caso 6: eccezione

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

Caso 7: promessa non mantenuta

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

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

    return fut.get();
}

Hai detto "... che sincronizza efficacemente il programma aspettando il risultato." . Che cosa significa "sincronizzare" qui? Cosa significa l'intera affermazione? Non riesco a capirlo. Nessuno dei significati di "sincronizzare" da questa voce del dizionario mi aiuta a capire la frase. "Aspettare" significa "sincronizzazione"? Ogni attesa si sincronizza? Penso di aver parzialmente capito cosa intendi, ma non sono sicuro di cosa intendi realmente .
Nawaz,

9
Bella risposta, grazie per il tuo aiuto. Riguardo alla parte di std :: async, ricordo che potremmo determinare che genererebbe un altro thread o funzionerebbe in modo sincrono con flag (std :: launch :: async, std :: launch :: deferred)
StereoMatching

1
@FelixDombek: l'inoltro perfetto ecc. std::functionHa molti costruttori; nessun motivo per non esporli al consumatore di my_task.
Kerrek SB,

1
@DaveedV .: Grazie per il feedback! Sì, questo è il caso di test 7: se distruggi la promessa senza impostare né valore né eccezione, la chiamata get()al futuro solleva un'eccezione. Chiarirò questo aggiungendo "prima che venga distrutto"; per favore fatemi sapere se è sufficientemente chiaro.
Kerrek SB,

3
Finalmente il got()mio futuredi grokking nella libreria di supporto del thread sulla promisetua incredibile spiegazione!
luna piena di sole

33

Bartosz Milewski fornisce un buon commento.

Il C ++ divide l'implementazione dei futures in un insieme di piccoli blocchi

std :: promise è una di queste parti.

Una promessa è un veicolo per passare il valore di ritorno (o un'eccezione) dal thread che esegue una funzione al thread che incassa sul futuro della funzione.

...

Un futuro è l'oggetto di sincronizzazione costruito attorno all'estremità ricevente del canale promettente.

Quindi, se si desidera utilizzare un futuro, si finisce con una promessa che si utilizza per ottenere il risultato dell'elaborazione asincrona.

Un esempio dalla pagina è:

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

4
Vedere la promessa nel costruttore del thread alla fine ha fatto cadere il centesimo. L'articolo di Bartosz non è forse il massimo, ma spiega come gli elementi si uniscono. Grazie.
Kerrek SB,

28

In una approssimazione approssimativa puoi considerare std::promisel'altra estremità di un std::future(questo è falso , ma per l'illustrazione puoi pensare come se fosse). L'estremità consumer del canale di comunicazione userebbe a std::futureper consumare il dato dallo stato condiviso, mentre il thread del produttore userebbe a std::promiseper scrivere nello stato condiviso.


12
@KerrekSB: std::asyncpuò concettualmente (questo non è obbligatorio per lo standard) inteso come una funzione che crea un std::promise, lo spinge in un pool di thread (di sorta, potrebbe essere un pool di thread, potrebbe essere un nuovo thread, ...) e restituisce associato std::futureal chiamante. Sul lato client si aspetterebbe std::futuree un thread sull'altra estremità calcolerebbe il risultato e lo memorizzerebbe in std::promise. Nota: lo standard richiede lo stato condiviso e std::futurel'esistenza, ma non l'esistenza, di un std::promisecaso d'uso specifico.
David Rodríguez - dribeas,

6
@KerrekSB: std::futurenon chiamerà joinil thread, ha un puntatore a uno stato condiviso che è l'effettivo buffer di comunicazione. Lo stato condiviso ha un meccanismo di sincronizzazione (probabilmente std::function+ std::condition_variableper bloccare il chiamante fino a quando non std::promiseviene soddisfatto. L'esecuzione del thread è ortogonale a tutto ciò, e in molte implementazioni potresti scoprire che std::asyncnon sono eseguite da nuovi thread che sono poi uniti, ma piuttosto da un pool di thread la cui durata si estende fino alla fine del programma
David Rodríguez - dribeas,

1
@ DavidRodríguez-dribeas: modifica le informazioni dai commenti nella tua risposta.
Marc Mutz - mmutz,

2
@JonathanWakely: Ciò non significa che debba essere eseguito in un nuovo thread, ma solo che deve essere eseguito in modo asincrono come se fosse eseguito in un thread appena creato. Il vantaggio principale di std::asyncè che la libreria di runtime può prendere le giuste decisioni per te in relazione al numero di thread da creare e nella maggior parte dei casi mi aspetterò runtime che utilizzano pool di thread. Attualmente VS2012 utilizza un pool di thread sotto il cofano e non viola la regola as-if . Si noti che ci sono pochissime garanzie che devono essere soddisfatte per questo particolare come se .
David Rodríguez - dribeas,

1
I locali dei thread devono essere reinizializzati, ma la regola as-if consente qualsiasi cosa (motivo per cui metto "come se" in corsivo :)
Jonathan Wakely,

11

std::promiseè il canale o il percorso per le informazioni da restituire dalla funzione asincrona. std::futureè il meccanismo di sincronizzazione che fa attendere il chiamante fino a quando il valore di ritorno trasportato in std::promiseè pronto (ovvero il suo valore è impostato all'interno della funzione).


8

Esistono in realtà 3 entità principali nell'elaborazione asincrona. Il C ++ 11 si concentra attualmente su 2 di essi.

Le cose fondamentali di cui hai bisogno per eseguire una logica asincrona sono:

  1. L' attività (pacchetto logico come oggetto funzione) che RUN '' da qualche parte ''.
  2. Il nodo di elaborazione effettivo : un thread, un processo, ecc. Che ESEGUE tali funzioni quando vengono fornite ad esso. Guarda il modello di progettazione "Comando" per avere una buona idea di come un pool di thread di lavoro di base lo fa.
  3. La maniglia del risultato : qualcuno ha bisogno di quel risultato e ha bisogno di un oggetto che lo ottenga per loro. Per OOP e altri motivi, qualsiasi attesa o sincronizzazione deve essere eseguita nelle API di questo handle.

C ++ 11 chiama le cose di cui parlo in (1) std::promisee quelle in (3) std::future. std::threadè l'unica cosa fornita pubblicamente per (2). Ciò è sfortunato perché i programmi reali devono gestire le risorse di thread e memoria e la maggior parte vorrà che le attività vengano eseguite su pool di thread invece di creare e distruggere un thread per ogni piccola attività (che quasi sempre causa hit di prestazioni non necessari da solo e può facilmente creare risorse la fame è anche peggio).

Secondo Herb Sutter e altri membri della fiducia del cervello in C ++ 11, ci sono piani sperimentali per aggiungere che std::executor- come in Java - sarà la base per pool di thread e configurazioni logicamente simili per (2). Forse lo vedremo in C ++ 2014, ma la mia scommessa è più simile a C ++ 17 (e Dio ci aiuti se si sbagliano lo standard per questi).


7

A std::promiseviene creato come punto finale per una coppia promessa / futura e std::future(creato dal get_future()metodo std :: promise usando il metodo) è l'altro punto finale. Questo è un semplice metodo one shot per fornire un modo per sincronizzare due thread mentre un thread fornisce dati a un altro thread attraverso un messaggio.

Puoi pensarlo come un thread crea una promessa di fornire dati e l'altro thread raccoglie la promessa in futuro. Questo meccanismo può essere utilizzato solo una volta.

Il meccanismo promessa / futuro è solo una direzione, dal thread che utilizza il set_value()metodo di std::promisea al thread che utilizza il get()di a std::futureper ricevere i dati. Viene generata un'eccezione se ilget() metodo di un futuro viene chiamato più di una volta.

Se il thread con il std::promisenon ha usato set_value()per mantenere la sua promessa, quando il secondo thread chiama get()il std::futureper raccogliere la promessa, il secondo thread andrà in uno stato di attesa fino a quando la promessa non sarà soddisfatta dal primo thread con std::promisequando usa il set_value()metodo per inviare i dati.

Con le proposte coroutine di specifica tecnica N4663 Linguaggi di programmazione - Estensioni C ++ per Coroutine e supporto del compilatore C ++ di Visual Studio 2017 co_await, è anche possibile utilizzare std::futuree std::asyncscrivere funzionalità coroutine. Vedi la discussione e l'esempio in https://stackoverflow.com/a/50753040/1466970 che ha una sezione che discute l'uso di std::futureconco_await .

Il seguente codice di esempio, una semplice applicazione console di Windows di Visual Studio 2013, mostra l'utilizzo di alcune classi / modelli di concorrenza C ++ 11 e altre funzionalità. Illustra un uso per promessa / futuro che funziona bene, thread autonomi che eseguiranno alcune attività e si fermeranno e un uso in cui è richiesto un comportamento più sincrono e, a causa della necessità di più notifiche, la coppia promessa / futura non funziona.

Una nota su questo esempio sono i ritardi aggiunti in vari punti. Questi ritardi sono stati aggiunti solo per assicurarsi che i vari messaggi stampati sulla console utilizzando std::coutfossero chiari e che il testo dei vari thread non fosse mescolato.

La prima parte di main()è la creazione di tre thread aggiuntivi e l'utilizzo std::promisee std::futurel'invio di dati tra i thread. Un punto interessante è dove il thread principale avvia un thread, T2, che attenderà i dati dal thread principale, farà qualcosa e quindi invierà i dati al terzo thread, T3, che quindi farà qualcosa e rimanderà i dati al filo principale.

La seconda parte main()crea due thread e un insieme di code per consentire più messaggi dal thread principale a ciascuno dei due thread creati. Non possiamo usarlo std::promisee std::futureper questo perché la coppia promessa / futura è un colpo solo e non può essere usata ripetutamente.

La fonte della lezione Sync_queueproviene dal linguaggio di programmazione C ++ di Stroustrup: 4a edizione.

// 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;
}

Questa semplice applicazione crea il seguente output.

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

1

La promessa è l'altra estremità del filo.

Immagina di dover recuperare il valore di un futureessere calcolato da un async. Tuttavia, non vuoi che venga calcolato nello stesso thread e non si genera nemmeno un thread "adesso" - forse il tuo software è stato progettato per scegliere un thread da un pool, quindi non sai chi lo farà eseguire il calcolo alla fine.

Ora, cosa passi a questo thread / classe / entità (ancora sconosciuto)? Non si passa il future, poiché questo è il risultato . Volete passare qualcosa che è connesso a futuree che rappresenta l'altra estremità del filo , quindi interrogherete semplicemente futuresenza alcuna conoscenza su chi effettivamente calcolerà / scriverà qualcosa.

Questo è il promise. È una maniglia collegata al tuo future. Se futureè un altoparlante e con get()te inizia ad ascoltare fino a quando non viene emesso un suono, promiseè un microfono ; ma non un microfono qualsiasi, è il microfono collegato con un solo filo all'altoparlante che si tiene in mano. Potresti sapere chi c'è dall'altra parte, ma non devi conoscerlo - lo dai e aspetti fino a quando l'altra parte dice qualcosa.


0

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

Spiegazione di una frase: furture :: get () attende promse :: set_value () per sempre.

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;
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.