O que é std :: promessa?

384

Estou bastante familiarizado com C ++ 11 do std::thread, std::asynce std::futurecomponentes (por exemplo, veja esta resposta ), que são simples e direta.

No entanto, não consigo entender bem o que std::promiseé, o que faz e em que situações é melhor usado. O documento padrão em si não contém muita informação além da sinopse de classe e nem apenas :: thread .

Alguém poderia, por favor, dar um exemplo sucinto e breve de uma situação em que std::promiseé necessária e onde é a solução mais idiomática?

Kerrek SB
fonte
2
Aqui está um código com ele em: en.cppreference.com/w/cpp/thread/future
chris
58
A versão realmente muito curta é: std::promiseé de onde std::futurevem. std::futureé o que permite recuperar um valor que lhe foi prometido . Quando você invoca get()um futuro, ele espera até o proprietário da pessoa std::promisecom a qual define o valor (invocando set_valuea promessa). Se a promessa for destruída antes que um valor seja definido e você invocar get()um futuro associado a essa promessa, você receberá uma std::broken_promiseexceção porque lhe foi prometido um valor, mas é impossível obter um.
James McNellis
14
Eu recomendo que, se você puder / quiser, dar uma olhada em C ++ Concurrency in Action por Anthony Williams
David Rodríguez - dribeas
32
O @KerrekSB std::broken_promiseé o identificador melhor nomeado na biblioteca padrão. E não há std::atomic_future.
Cubbi
3
Downvoter, gostaria de explicar sua objeção?
precisa saber é o seguinte

Respostas:

182

Nas palavras de [futures.state] a std::futureé um objeto de retorno assíncrono ("um objeto que lê resultados de um estado compartilhado") e a std::promiseé um provedor assíncrono ("um objeto que fornece um resultado para um estado compartilhado"), ou seja, um promessa é a coisa em que você define um resultado, para que você possa obtê- lo no futuro associado.

O provedor assíncrono é o que inicialmente cria o estado compartilhado ao qual um futuro se refere. std::promiseé um tipo de provedor assíncrono, std::packaged_taské outro, e os detalhes internos de std::asyncé outro. Cada um deles pode criar um estado compartilhado e fornecer um std::futureque compartilhe esse estado, além de poder prepará-lo.

std::asyncé um utilitário de conveniência de nível superior que fornece um objeto de resultado assíncrono e cuida internamente da criação do provedor assíncrono e da preparação do estado compartilhado quando a tarefa é concluída. Você pode emular com um std::packaged_task(ou std::binda std::promise) e um std::threadmas é mais seguro e fácil de usar std::async.

std::promiseé um pouco mais baixo, pois quando você deseja passar um resultado assíncrono para o futuro, mas o código que prepara o resultado não pode ser agrupado em uma única função adequada para a passagem std::async. Por exemplo, você pode ter uma matriz de vários se promiseassociados futuree ter um único encadeamento que faz vários cálculos e define um resultado para cada promessa. asyncsó permitiria que você retornasse um único resultado; para retornar vários, você precisaria ligar asyncvárias vezes, o que poderia desperdiçar recursos.

Jonathan Wakely
fonte
10
Pode desperdiçar recursos? Pode estar incorreto, se esse código não puder ser paralelo.
Puppy
"retorno assíncrono" e "resultado de leituras do estado compartilhado" são ortogonais, o que torna a primeira frase um pouco confusa. Você quer dizer que o compartilhamento do estado é entre o futuro e a promessa? Se sim, diga isso explicitamente desde o início.
einpoklum
@einpoklum Por que você parou de ler "objeto de retorno assíncrono" antes da última palavra? Estou citando a terminologia do padrão. A futureé um exemplo concreto de um objeto de retorno assíncrono , que é um objeto que lê um resultado retornado de forma assíncrona, por meio do estado compartilhado. A promiseé um exemplo concreto de um provedor assíncrono , que é um objeto que grava um valor no estado compartilhado, que pode ser lido de forma assíncrona. Eu quis dizer o que escrevi.
Jonathan Wakely
496

Entendo a situação um pouco melhor agora (em grande parte devido às respostas aqui!), Então pensei em adicionar um pouco de minha própria redação.


Existem dois conceitos distintos, embora relacionados, no C ++ 11: Computação assíncrona (uma função que é chamada em outro lugar) e execução simultânea (um encadeamento , algo que funciona simultaneamente). Os dois são conceitos um tanto ortogonais. A computação assíncrona é apenas um tipo diferente de chamada de função, enquanto um encadeamento é um contexto de execução. Os threads são úteis por si só, mas para os fins desta discussão, eu os tratarei como um detalhe de implementação.


Existe uma hierarquia de abstração para computação assíncrona. Por exemplo, suponha que tenhamos uma função que aceite alguns argumentos:

int foo(double, char, bool);

Primeiro, temos o modelo std::future<T>, que representa um valor futuro do tipo T. O valor pode ser recuperado através da função membro get(), que efetivamente sincroniza o programa, aguardando o resultado. Como alternativa, um futuro suporta wait_for(), que pode ser usado para verificar se o resultado já está disponível. Os futuros devem ser considerados como a substituição assíncrona de tipos de retorno comuns. Para nossa função de exemplo, esperamos a std::future<int>.

Agora, para a hierarquia, do nível mais alto para o mais baixo:

  1. std::async: A maneira mais conveniente e direta de executar uma computação assíncrona é através do asyncmodelo de função, que retorna o futuro correspondente imediatamente:

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

    Temos muito pouco controle sobre os detalhes. Em particular, nem sabemos se a função é executada simultaneamente, em série get()ou por alguma outra magia negra. No entanto, o resultado é facilmente obtido quando necessário:

    auto res = fut.get();  // is an int
  2. Podemos agora considerar como implementar algo parecido async, mas de uma forma que nós controlar. Por exemplo, podemos insistir que a função seja executada em um thread separado. Já sabemos que podemos fornecer um thread separado por meio da std::threadclasse

    O próximo nível mais baixo de abstração faz exatamente isso: std::packaged_task. Este é um modelo que envolve uma função e fornece um futuro para o valor de retorno das funções, mas o próprio objeto é passível de chamada e a chamada é a critério do usuário. Podemos configurá-lo assim:

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

    O futuro fica pronto quando chamamos a tarefa e a chamada é concluída. Esse é o trabalho ideal para um thread separado. Nós apenas temos que ter certeza de mover a tarefa para o thread:

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

    O encadeamento começa a ser executado imediatamente. Podemos detachfazê-lo, ou tê- joinlo no final do escopo, ou sempre que necessário (por exemplo, usando o scoped_threadwrapper de Anthony Williams , que realmente deveria estar na biblioteca padrão). Os detalhes do uso std::threadnão nos interessam aqui; apenas junte-se ou desanexe-o threventualmente. O que importa é que, sempre que a chamada da função termina, nosso resultado está pronto:

    auto res = fut.get();  // as before
  3. Agora, estamos no nível mais baixo: como implementar a tarefa empacotada? É aqui que std::promiseentra. A promessa é o alicerce para a comunicação com o futuro. As principais etapas são estas:

    • O segmento de chamada faz uma promessa.

    • O segmento de chamada obtém um futuro da promessa.

    • A promessa, juntamente com os argumentos da função, são movidos para um thread separado.

    • O novo thread executa a função e cumpre a promessa.

    • O encadeamento original recupera o resultado.

    Como exemplo, aqui está nossa "tarefa empacotada":

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

    O uso desse modelo é essencialmente o mesmo que o de std::packaged_task. Observe que mover a tarefa inteira implica mover a promessa. Em situações mais ad-hoc, também é possível mover um objeto de promessa explicitamente para o novo encadeamento e torná-lo um argumento de função da função de encadeamento, mas um invólucro de tarefa como o descrito acima parece ser uma solução mais flexível e menos intrusiva.


Fazendo exceções

Promessas estão intimamente relacionadas a exceções. A interface de uma promessa por si só não é suficiente para transmitir seu estado completamente; portanto, são lançadas exceções sempre que uma operação em uma promessa não faz sentido. Todas as exceções são do tipo std::future_errorderivadas de std::logic_error. Primeiro, uma descrição de algumas restrições:

  • Uma promessa construída por padrão é inativa. Promessas inativas podem morrer sem conseqüências.

  • Uma promessa se torna ativa quando um futuro é obtido via get_future(). No entanto, apenas um futuro pode ser obtido!

  • Uma promessa deve ser cumprida via set_value()ou ter uma exceção definida set_exception()antes de sua vida útil terminar, para que seu futuro seja consumido. Uma promessa satisfeita pode morrer sem conseqüências e get()se tornar disponível no futuro. Uma promessa com uma exceção aumentará a exceção armazenada quando solicitado get()no futuro. Se a promessa morrer sem valor nem exceção, o convite get()ao futuro criará uma exceção de "promessa quebrada".

Aqui está uma pequena série de testes para demonstrar esses vários comportamentos excepcionais. Primeiro, o chicote:

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

Agora vamos aos testes.

Caso 1: promessa inativa

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

Caso 2: promessa ativa, não utilizada

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

Caso 3: Muitos futuros

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 cumprida

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: Muita satisfação

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();
}

A mesma exceção é lançada se houver mais do que um de qualquer de set_valueou set_exception.

Caso 6: Exceção

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 quebrada

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
fonte
Você disse "... que efetivamente sincroniza o programa aguardando o resultado". . O que significa "sincroniza" aqui? O que significa toda a declaração? Eu sou incapaz de entender isso. Nenhum dos significados de "sincronizar" nesta entrada do dicionário me ajuda a entender a frase. Apenas "aguardando" significa "sincronização"? Toda espera é sincronizada? Acho que entendo parcialmente o que você quer dizer, mas não sei o que você realmente quer dizer.
Nawaz
9
Resposta agradável, obrigado por sua ajuda. Sobre a parte do std :: async, lembro que poderíamos determinar que ele geraria outro thread ou funcionaria em sincronia com o sinalizador (std :: launch :: async, std :: launch :: adiado)
StereoMatching
11
@ FelixDombek: O encaminhamento perfeito etc. std::functiontem muitos construtores; Não há razão para não expô-los ao consumidor de my_task.
Kerrek SB /
11
@DaveedV .: Obrigado pelo feedback! Sim, esse é o caso de teste 7: se você destruir a promessa sem definir valor ou exceção, a chamada get()no futuro gera uma exceção. Vou esclarecer isso adicionando "antes que seja destruído"; informe-me se isso estiver suficientemente claro.
precisa saber é o seguinte
3
Finalmente got()minha futurede grokking a biblioteca de suporte linha no promisede sua explicação incrível!
lua ensolarada
33

Bartosz Milewski fornece uma boa descrição.

C ++ divide a implementação de futuros em um conjunto de pequenos blocos

std :: promessa é uma dessas partes.

Uma promessa é um veículo para passar o valor de retorno (ou uma exceção) do encadeamento que executa uma função para o encadeamento que lucra com o futuro da função.

...

Um futuro é o objeto de sincronização construído em torno da extremidade receptora do canal de promessa.

Portanto, se você quiser usar um futuro, terá a promessa de obter o resultado do processamento assíncrono.

Um exemplo da página é:

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
fonte
4
Ver a promessa no construtor da linha finalmente fez o centavo cair. O artigo de Bartosz talvez não seja o melhor, mas explica como os elementos se unem. Obrigado.
Kerrek SB
28

Em uma aproximação aproximada, você pode considerar std::promiseo outro extremo de a std::future(isso é falso , mas para ilustração, você pode pensar como se fosse). O consumidor final do canal de comunicação usaria a std::futurepara consumir o dado do estado compartilhado, enquanto o encadeamento produtor usaria a std::promisepara gravar no estado compartilhado.

David Rodríguez - dribeas
fonte
12
@KerrekSB: std::asyncpode conceitualmente (isso não é obrigatório pelo padrão) entender como uma função que cria a std::promise, envia isso para um pool de threads (das sortes, pode ser um pool de threads, pode ser um novo thread, ...) e retorna o associado std::futureao chamador. No lado do cliente, você esperaria no std::futuree um thread do outro lado calcularia o resultado e o armazenaria no std::promise. Nota: o padrão requer o estado compartilhado e std::futurea existência, mas não a, de um std::promiseneste caso de uso específico.
David Rodríguez - dribeas
6
@KerrekSB: std::futurenão chama joino thread, ele possui um ponteiro para um estado compartilhado, que é o buffer de comunicação real. O estado compartilhado possui um mecanismo de sincronização (provavelmente std::function+ std::condition_variablepara bloquear o chamador até que o processo std::promiseseja concluído. A execução do encadeamento é ortogonal a tudo isso e, em muitas implementações, você pode achar que std::asyncnão são executados por novos encadeamentos que são unidos, mas sim por um pool de segmentos cuja vida útil se estende até o final do programa.
David Rodríguez - dribeas
11
@ DavidRodríguez-dribeas: edite as informações dos comentários em sua resposta.
Marc Mutz - mmutz
2
@ JonathanWakely: Isso não significa que ele deve ser executado em um novo thread, apenas que ele deve ser executado de forma assíncrona como se fosse executado em um thread recém-criado. A principal vantagem std::asyncé que a biblioteca de tempo de execução pode tomar as decisões corretas para você em relação ao número de threads a serem criados e, na maioria dos casos, esperarei tempos de execução que usem conjuntos de threads. Atualmente, o VS2012 usa um pool de threads sob o capô e não viola a regra como se . Observe que há muito poucas garantias que precisam ser cumpridas para esse particular como se .
David Rodríguez - dribeas
11
Locals Tópico precisa ser inicializado-re, mas o as-se a regra permite que qualquer coisa (que é por isso que eu coloquei "como se" em itálico :)
Jonathan Wakely
11

std::promiseé o canal ou caminho para que as informações sejam retornadas da função assíncrona. std::futureé o mecanismo de sincronização que faz o chamador esperar até que o valor de retorno carregado no std::promiseesteja pronto (ou seja, seu valor é definido dentro da função).

kjp
fonte
8

Existem realmente três entidades principais no processamento assíncrono. O C ++ 11 atualmente se concentra em 2 deles.

As principais coisas que você precisa para executar alguma lógica de forma assíncrona são:

  1. A tarefa (lógica compactada como algum objeto functor) que será executada 'em algum lugar'.
  2. O nó de processamento real - um encadeamento, um processo etc. que EXECUTA esses functores quando são fornecidos a ele. Veja o padrão de design "Comando" para ter uma boa idéia de como um pool de threads de trabalho básico faz isso.
  3. O identificador do resultado : alguém precisa desse resultado e precisa de um objeto que o obtenha para eles. Por OOP e outros motivos, qualquer espera ou sincronização deve ser feita nas APIs deste identificador.

O C ++ 11 chama as coisas de que falo em (1) std::promisee as de (3) std::future. std::threadé a única coisa fornecida publicamente (2). Isso é lamentável, porque programas reais precisam gerenciar recursos de encadeamento e memória, e a maioria deseja que as tarefas sejam executadas em conjuntos de encadeamentos, em vez de criar e destruir um encadeamento para cada pequena tarefa (que quase sempre causa resultados de desempenho desnecessários por si só e pode facilmente criar recursos fome ainda pior).

De acordo com Herb Sutter e outros membros do C ++ 11 brain trust, existem planos preliminares de adicionar algo std::executorcomo o Java - será a base para pools de threads e configurações logicamente semelhantes para (2). Talvez o veremos no C ++ 2014, mas minha aposta é mais parecida com o C ++ 17 (e Deus nos ajude se eles não cumprirem o padrão).

Zack Yezek
fonte
7

A std::promiseé criado como um ponto final para um par promessa / futuro e o std::future(criado a partir do std :: promessa usando o get_future()método) é o outro ponto final. Esse é um método simples e único de fornecer uma maneira de sincronizar dois threads, pois um thread fornece dados para outro thread por meio de uma mensagem.

Você pode pensar nisso como um segmento cria uma promessa de fornecer dados e o outro segmento coleta a promessa no futuro. Este mecanismo pode ser usado apenas uma vez.

O mecanismo de promessa / futuro é apenas uma direção, do encadeamento que usa o set_value()método de std::promisea ao encadeamento que usa o get()de a std::futurepara receber os dados. Uma exceção será gerada se o get()método de um futuro for chamado mais de uma vez.

Se o segmento com o std::promisenão tiver sido usado set_value()para cumprir sua promessa, quando o segundo segmento chamar get()o std::futurepara coletar a promessa, o segundo segmento entrará em um estado de espera até que a promessa seja cumprida pelo primeiro segmento e std::promisequando ele usar o set_value()método para enviar os dados.

Com as corotinas propostas das Linguagens de Programação da Especificação Técnica N4663 - Extensões C ++ para Coroutines e o suporte do compilador C ++ do Visual Studio 2017 co_await, também é possível usar std::futuree std::asyncgravar a funcionalidade da corotina. Consulte a discussão e o exemplo em https://stackoverflow.com/a/50753040/1466970, que possui uma seção que discute o uso de std::futurewith co_await.

O código de exemplo a seguir, um aplicativo simples do console do Visual Studio 2013 para Windows, mostra o uso de algumas das classes / modelos de simultaneidade C ++ 11 e outras funcionalidades. Ilustra um uso para promessa / futuro que funciona bem, threads autônomos que executam algumas tarefas e param, e um uso em que é necessário um comportamento mais síncrono e devido à necessidade de várias notificações, o par promessa / futuro não funciona.

Uma observação sobre este exemplo são os atrasos adicionados em vários locais. Esses atrasos foram adicionados apenas para garantir que as várias mensagens impressas no console std::coutfossem claras e que o texto dos vários segmentos não seria entremeado.

A primeira parte main()é criar três threads adicionais e usar std::promisee std::futureenviar dados entre os threads. Um ponto interessante é onde o encadeamento principal inicia um encadeamento, T2, que aguarda os dados do encadeamento principal, faz alguma coisa e envia os dados para o terceiro encadeamento, T3, que faz algo e envia os dados de volta para o encadeamento. rosca principal.

A segunda parte do main()cria dois threads e um conjunto de filas para permitir várias mensagens do thread principal para cada um dos dois threads criados. Nós não podemos usar std::promisee std::futurepara isso, porque a promessa / futuro duo são um disparo e não pode ser usado repetidamente.

A fonte da classe Sync_queueé da The C ++ Programming Language: 4th Edition, da Stroustrup.

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

Este aplicativo simples cria a seguinte saída.

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
fonte
1

A promessa é a outra extremidade do fio.

Imagine que você precisa recuperar o valor de um futureser calculado por um async. No entanto, você não deseja que ele seja computado no mesmo encadeamento e nem gera um encadeamento "agora" - talvez seu software tenha sido projetado para escolher um encadeamento de um pool, para que você não saiba quem irá execute a computação no final.

Agora, o que você passa para esse thread / classe / entidade (ainda desconhecida)? Você não passa future, pois esse é o resultado . Você deseja passar algo que esteja conectado ao futuree que represente a outra extremidade do fio , portanto, você apenas consultará o futuresem conhecimento sobre quem realmente calculará / gravará algo.

Este é o promise. É uma alça conectada ao seu future. Se futurefor um alto - falante , e com get()você começar a ouvir até que algum som saia, promiseé um microfone ; mas não apenas qualquer microfone, é o microfone conectado com um único fio ao alto-falante que você segura. Você pode saber quem está do outro lado, mas não precisa saber - basta dar e esperar até que a outra parte diga alguma coisa.

Narcolessico
fonte
0

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

Uma explicação da frase: furture :: get () espera promse :: set_value () para 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;
}
Zhang
fonte