¿Qué es std :: promise?

384

Estoy bastante familiarizado con C ++ 11 del std::thread, std::asyncy std::futurelos componentes (por ejemplo, véase esta respuesta ), que son sencillas.

Sin embargo, no puedo entender qué std::promisees, qué hace y en qué situaciones se usa mejor. El documento estándar en sí no contiene mucha información más allá de su sinopsis de clase, y tampoco lo hace solo :: thread .

¿Podría alguien dar un breve y sucinto ejemplo de una situación en la que std::promisese necesita y donde es la solución más idiomática?

Kerrek SB
fuente
2
Aquí hay un código con él en: en.cppreference.com/w/cpp/thread/future
chris
58
La versión muy, muy corta es: std::promisees de dónde std::futureviene. std::futurees lo que le permite recuperar un valor que se le ha prometido . Cuando llama get()a un futuro, espera hasta que el propietario del std::promisecon el que establece el valor (llamando set_valuea la promesa). Si la promesa se destruye antes de establecer un valor, y luego invocas get()un futuro asociado con esa promesa, obtendrás una std::broken_promiseexcepción porque se te prometió un valor, pero es imposible que lo consigas.
James McNellis
14
Recomiendo que, si puede / quiera, eche un vistazo a C ++ Concurrency in Action por Anthony Williams
David Rodríguez - dribeas
32
@KerrekSB std::broken_promisees el identificador mejor nombrado en la biblioteca estándar. Y allí no está std::atomic_future.
Cubbi
3
Votante, ¿quieres explicar tu objeción?
Kerrek SB

Respuestas:

182

En palabras de [futures.state] a std::futurees un objeto de retorno asíncrono ("un objeto que lee resultados de un estado compartido") y a std::promisees un proveedor asíncrono ("un objeto que proporciona un resultado a un estado compartido"), es decir, un Promesa es aquello en lo que establece un resultado, para que pueda obtenerlo del futuro asociado.

El proveedor asincrónico es lo que inicialmente crea el estado compartido al que se refiere un futuro. std::promisees un tipo de proveedor asincrónico, std::packaged_taskes otro, y el detalle interno de std::asynces otro. Cada uno de ellos puede crear un estado compartido y darle un estado std::futureque lo comparta, y puede preparar el estado.

std::asynces una utilidad de conveniencia de nivel superior que le brinda un objeto de resultado asíncrono y se encarga internamente de crear el proveedor asíncrono y de preparar el estado compartido cuando se completa la tarea. Puede emularlo con un std::packaged_task(o std::bindy a std::promise) y un std::threadpero es más seguro y fácil de usar std::async.

std::promisees un poco más bajo, para cuando desea pasar un resultado asincrónico al futuro, pero el código que hace que el resultado esté listo no se puede incluir en una sola función adecuada para pasar std::async. Por ejemplo, puede tener una matriz de varios promisesy asociados futurey tener un solo hilo que hace varios cálculos y establece un resultado en cada promesa. asyncsolo le permitiría devolver un solo resultado, para devolver varios necesitaría llamar asyncvarias veces, lo que podría desperdiciar recursos.

Jonathan Wakely
fuente
10
¿Podría desperdiciar recursos? Puede ser incorrecto, si ese código no puede ser paralelo.
Cachorro
"retorno asíncrono" y "resultado de lecturas del estado compartido" son en su mayoría ortogonales, lo que hace que la primera oración sea un poco confusa. ¿Quieres decir que compartir el estado es entre el futuro y la promesa? Si es así, dígalo explícitamente desde el principio.
einpoklum
@einpoklum ¿por qué dejaste de leer "objeto de retorno asincrónico" antes de la última palabra? Cito la terminología del estándar. A futurees un ejemplo concreto de un objeto de retorno asíncrono , que es un objeto que lee un resultado que se devolvió de forma asíncrona, a través del estado compartido. A promisees un ejemplo concreto de un proveedor asincrónico , que es un objeto que escribe un valor en el estado compartido, que se puede leer de forma asincrónica. Quise decir lo que escribí.
Jonathan Wakely
496

Ahora entiendo la situación un poco mejor (¡en gran medida debido a las respuestas aquí!), Así que pensé agregar un poco de mi propia reseña.


Hay dos conceptos distintos, aunque relacionados, en C ++ 11: computación asincrónica (una función que se llama en otro lugar) y ejecución concurrente (un hilo , algo que funciona simultáneamente). Los dos son conceptos algo ortogonales. El cálculo asincrónico es solo un sabor diferente de la llamada de función, mientras que un hilo es un contexto de ejecución. Los hilos son útiles por derecho propio, pero para el propósito de esta discusión, los trataré como un detalle de implementación.


Existe una jerarquía de abstracción para el cálculo asincrónico. Por ejemplo, supongamos que tenemos una función que toma algunos argumentos:

int foo(double, char, bool);

En primer lugar, tenemos la plantilla std::future<T>, que representa un valor futuro de tipo T. El valor se puede recuperar a través de la función miembro get(), que sincroniza efectivamente el programa esperando el resultado. Alternativamente, un futuro admite wait_for(), que se puede utilizar para investigar si el resultado ya está disponible o no. Los futuros deben considerarse como el reemplazo asíncrono directo para los tipos de retorno ordinarios. Para nuestra función de ejemplo, esperamos a std::future<int>.

Ahora, en la jerarquía, del nivel más alto al más bajo:

  1. std::async: La forma más conveniente y directa de realizar un cálculo asincrónico es a través de la asyncplantilla de función, que devuelve el futuro coincidente de inmediato:

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

    Tenemos muy poco control sobre los detalles. En particular, ni siquiera sabemos si la función se ejecuta simultáneamente, en serie get()o por alguna otra magia negra. Sin embargo, el resultado se obtiene fácilmente cuando es necesario:

    auto res = fut.get();  // is an int
  2. Ahora podemos considerar la forma de poner en práctica algo así async, pero de una manera que nos controlar. Por ejemplo, podemos insistir en que la función se ejecute en un hilo separado. Ya sabemos que podemos proporcionar un hilo separado por medio de la std::threadclase.

    El siguiente nivel de abstracción más bajo hace exactamente eso: std::packaged_task. Esta es una plantilla que envuelve una función y proporciona un futuro para el valor de retorno de las funciones, pero el objeto en sí es invocable, y llamarlo queda a discreción del usuario. Podemos configurarlo así:

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

    El futuro se prepara una vez que llamamos a la tarea y la llamada se completa. Este es el trabajo ideal para un hilo separado. Solo tenemos que asegurarnos de mover la tarea al hilo:

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

    El hilo comienza a ejecutarse inmediatamente. Podemos detachhacerlo, o tenerlo joinal final del alcance, o cuando sea (por ejemplo, usando el scoped_threadcontenedor de Anthony Williams , que realmente debería estar en la biblioteca estándar). Sin std::threadembargo, los detalles del uso no nos conciernen aquí; solo asegúrese de unirse o separarse threventualmente. Lo importante es que cada vez que finalice la llamada a la función, nuestro resultado esté listo:

    auto res = fut.get();  // as before
  3. Ahora estamos en el nivel más bajo: ¿cómo implementaríamos la tarea empaquetada? Aquí es donde std::promiseentra en juego. La promesa es la piedra angular para comunicarse con un futuro. Los pasos principales son estos:

    • El hilo conductor hace una promesa.

    • El hilo conductor llama a un futuro de la promesa.

    • La promesa, junto con los argumentos de la función, se mueven a un hilo separado.

    • El nuevo hilo ejecuta la función y cumple la promesa.

    • El hilo original recupera el resultado.

    Como ejemplo, aquí está nuestra propia "tarea empaquetada":

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

    El uso de esta plantilla es esencialmente el mismo que el de std::packaged_task. Tenga en cuenta que mover toda la tarea supone mover la promesa. En situaciones más ad-hoc, uno también podría mover un objeto de promesa explícitamente al nuevo hilo y convertirlo en un argumento de función de la función del hilo, pero un contenedor de tareas como el anterior parece una solución más flexible y menos intrusiva.


Haciendo excepciones

Las promesas están íntimamente relacionadas con las excepciones. La interfaz de una promesa por sí sola no es suficiente para transmitir su estado por completo, por lo que se lanzan excepciones cuando una operación de una promesa no tiene sentido. Todas las excepciones son de tipo std::future_error, que deriva de std::logic_error. En primer lugar, una descripción de algunas restricciones:

  • Una promesa construida por defecto está inactiva. Las promesas inactivas pueden morir sin consecuencias.

  • Una promesa se activa cuando se obtiene un futuro a través de get_future(). Sin embargo, ¡solo se puede obtener un futuro!

  • Una promesa debe cumplirse set_value()o tener una excepción establecida set_exception()antes de que termine su vida útil si se va a consumir su futuro. Una promesa satisfecha puede morir sin consecuencias, y get()estará disponible en el futuro. Una promesa con una excepción aumentará la excepción almacenada cuando se solicite get()en el futuro. Si la promesa muere sin valor ni excepción, invocar get()el futuro generará una excepción de "promesa rota".

Aquí hay una pequeña serie de pruebas para demostrar estos diversos comportamientos excepcionales. Primero, el arnés:

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

Ahora a las pruebas.

Caso 1: promesa inactiva

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

Caso 2: promesa activa, no utilizada

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

Caso 3: demasiados 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: promesa satisfecha

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: demasiada satisfacción

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

Se produce la misma excepción si hay más de uno de cualquiera de set_valueo set_exception.

Caso 6: Excepción

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: promesa rota

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
fuente
Dijiste "... lo que efectivamente sincroniza el programa esperando el resultado". . ¿Qué significa "sincronizar" aquí? ¿Qué significa toda la declaración? No puedo entender esto. Ninguno de los significados de "sincronizar" de esta entrada del diccionario me ayuda a entender la oración. ¿Solo "esperar" significa "sincronización"? ¿Cada espera se sincroniza? Creo que entiendo parcialmente lo que quieres decir, pero no estoy seguro de lo que realmente quieres decir.
Nawaz
99
Buena respuesta, gracias por su ayuda. Acerca de la parte de std :: async, recuerdo que podríamos determinar que generaría otro hilo o funcionaría sincrónicamente con la bandera (std :: launch :: async, std :: launch :: deferred)
StereoMatching
1
@FelixDombek: el reenvío perfecto, etc. std::functiontiene muchos constructores; no hay razón para no exponerlos al consumidor de my_task.
Kerrek SB
1
@DaveedV .: ¡Gracias por los comentarios! Sí, ese es el caso de prueba 7: si destruye la promesa sin establecer un valor o una excepción, entonces recurrir get()al futuro genera una excepción. Aclararé esto agregando "antes de que sea destruido"; por favor avíseme si eso está suficientemente claro.
Kerrek SB
3
Finalmente, ¡ got()he futureencontrado la biblioteca de soporte de hilos en promisetu increíble explicación!
luna soleada
33

Bartosz Milewski ofrece una buena crítica.

C ++ divide la implementación de futuros en un conjunto de bloques pequeños

std :: promise es una de estas partes.

Una promesa es un vehículo para pasar el valor de retorno (o una excepción) del subproceso que ejecuta una función al subproceso que cobra en el futuro de la función.

...

Un futuro es el objeto de sincronización construido alrededor del extremo receptor del canal de promesa.

Por lo tanto, si desea utilizar un futuro, termina con una promesa que utiliza para obtener el resultado del procesamiento asincrónico.

Un ejemplo de la página es:

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
fuente
44
Ver la promesa en el constructor del hilo finalmente hizo caer el centavo. El artículo de Bartosz quizás no sea el mejor, pero explica cómo se unen los elementos. Gracias.
Kerrek SB
28

En una aproximación aproximada, puede considerarlo std::promisecomo el otro extremo de a std::future(esto es falso , pero por ejemplo, puede pensar como si lo fuera). El consumidor final del canal de comunicación usaría a std::futurepara consumir el dato desde el estado compartido, mientras que el hilo productor usaría a std::promisepara escribir en el estado compartido.

David Rodríguez - dribeas
fuente
12
@KerrekSB: std::asyncpuede conceptualmente (esto no es obligatorio por el estándar) entendido como una función que crea un std::promise, lo empuja a un grupo de subprocesos (más o menos, podría ser un grupo de subprocesos, podría ser un nuevo subproceso, ...) y devuelve el asociado std::futurea la persona que llama. En el lado del cliente, esperaría en el std::futurey un hilo en el otro extremo calcularía el resultado y lo almacenaría en el std::promise. Nota: el estándar requiere el estado compartido y std::futurela existencia de a std::promiseen este caso de uso en particular, pero no .
David Rodríguez - dribeas
66
@KerrekSB: std::futureno llamará joinal hilo, tiene un puntero a un estado compartido que es el búfer de comunicación real. El estado compartido tiene un mecanismo de sincronización (probablemente std::function+ std::condition_variablepara bloquear a la persona que llama hasta que std::promisese complete. La ejecución del subproceso es ortogonal a todo esto, y en muchas implementaciones puede encontrar que std::asyncno se ejecutan nuevos subprocesos que luego se unen, pero más bien por un grupo de hilos cuya vida útil se extiende hasta el final del programa.
David Rodríguez - dribeas
1
@ DavidRodríguez-dribeas: edite la información de los comentarios en su respuesta.
Marc Mutz - mmutz
2
@ JonathanWakely: Eso no significa que deba ejecutarse en un nuevo subproceso, solo que debe ejecutarse de forma asincrónica como si se ejecutara en un nuevo subproceso creado. La principal ventaja de std::asynces que la biblioteca de tiempo de ejecución puede tomar las decisiones correctas para usted con respecto a la cantidad de subprocesos a crear y, en la mayoría de los casos, esperaré tiempos de ejecución que usen grupos de subprocesos. Actualmente VS2012 usa un grupo de subprocesos debajo del capó, y no viola la regla de si-como . Tenga en cuenta que hay muy pocas garantías que deben cumplirse para este particular como si .
David Rodríguez - dribeas
1
Los locales de subprocesos deben reiniciarse, pero la regla como si permitiera cualquier cosa (por eso pongo "como si" en cursiva :)
Jonathan Wakely
11

std::promisees el canal o la vía para que la información se devuelva desde la función asíncrona. std::futurees el mecanismo de sincronización que hace que la persona que llama espere hasta que el valor de retorno transportado std::promiseesté listo (lo que significa que su valor se establece dentro de la función).

kjp
fuente
8

Realmente hay 3 entidades centrales en el procesamiento asincrónico. C ++ 11 actualmente se centra en 2 de ellos.

Las cosas principales que necesita para ejecutar algo de lógica de forma asincrónica son:

  1. La tarea (lógica empaquetada como algún objeto de función) que CORRE "en algún lugar".
  2. El nodo de procesamiento real : un subproceso, un proceso, etc. que ejecuta tales functores cuando se le proporcionan. Mire el patrón de diseño "Comando" para una buena idea de cómo un grupo de subprocesos de trabajo básico hace esto.
  3. El identificador de resultados : alguien necesita ese resultado y necesita un objeto que lo OBTENGA por ellos. Para OOP y otras razones, cualquier espera o sincronización debe hacerse en las API de este identificador.

C ++ 11 llama a las cosas de las que hablo en (1) std::promisey a las de (3) std::future. std::threades lo único que se proporciona públicamente para (2). Esto es desafortunado porque los programas reales necesitan administrar recursos de subprocesos y memoria, y la mayoría querrá que las tareas se ejecuten en grupos de subprocesos en lugar de crear y destruir un subproceso para cada pequeña tarea (que casi siempre causa problemas de rendimiento innecesarios por sí mismo y puede crear recursos fácilmente inanición que es aún peor).

De acuerdo con Herb Sutter y otros en la confianza del cerebro de C ++ 11, existen planes tentativos para agregar que std::executor, al igual que en Java, será la base para grupos de subprocesos y configuraciones lógicamente similares para (2). Tal vez lo veamos en C ++ 2014, pero mi apuesta es más como C ++ 17 (y que Dios nos ayude si no cumplen con el estándar para estos).

Zack Yezek
fuente
7

A std::promisese crea como un punto final para un par promesa / futuro y el std::future(creado a partir de std :: promise usando el get_future()método) es el otro punto final. Este es un método simple, de una sola toma, para proporcionar una manera de que dos hilos se sincronicen, ya que un hilo proporciona datos a otro hilo a través de un mensaje.

Puede pensar que un hilo crea una promesa de proporcionar datos y el otro hilo recoge la promesa en el futuro. Este mecanismo solo se puede usar una vez.

El mecanismo de promesa / futuro es solo una dirección, desde el hilo que usa el set_value()método de a std::promisehasta el hilo que usa el get()de a std::futurepara recibir los datos. Se genera una excepción si el get()método de un futuro se llama más de una vez.

Si el subproceso con el std::promiseno se ha utilizado set_value()para cumplir su promesa, cuando el segundo subproceso llame get()al std::futurepara recopilar la promesa, el segundo subproceso pasará a un estado de espera hasta que el primer subproceso con la promesa cumpla la promesa std::promisecuando utilice el set_value()método para enviar los datos.

Con las corutinas propuestas de los lenguajes de programación de la especificación técnica N4663 - Extensiones C ++ para corutinas y el soporte del compilador C ++ de Visual Studio 2017 co_await, también es posible usar std::futurey std::asyncescribir la funcionalidad corutina. Consulte la discusión y el ejemplo en https://stackoverflow.com/a/50753040/1466970 que tiene como una sección que discute el uso de std::futurewith co_await.

El siguiente código de ejemplo, una simple aplicación de consola de Windows de Visual Studio 2013, muestra el uso de algunas de las clases / plantillas de concurrencia de C ++ 11 y otras funciones. Ilustra un uso para promesa / futuro que funciona bien, hilos autónomos que harán alguna tarea y se detendrán, y un uso donde se requiere un comportamiento más sincrónico y debido a la necesidad de múltiples notificaciones, el par promesa / futuro no funciona.

Una nota sobre este ejemplo son las demoras agregadas en varios lugares. Estos retrasos se agregaron solo para asegurarse de que los diversos mensajes impresos en la consola que se usan std::coutsean claros y que el texto de varios hilos no se mezcle.

La primera parte de esto main()es crear tres hilos adicionales y usar std::promisey std::futureenviar datos entre los hilos. Un punto interesante es donde el subproceso principal inicia un subproceso, T2, que esperará los datos del subproceso principal, hará algo y luego enviará datos al tercer subproceso, T3, que luego hará algo y enviará datos de regreso al Hilo principal.

La segunda parte de main()crea dos hilos y un conjunto de colas para permitir múltiples mensajes desde el hilo principal a cada uno de los dos hilos creados. No podemos usarlo std::promisey std::futurepara esto porque el dúo prometedor / futuro es de una sola vez y no se puede usar repetidamente.

La fuente de la clase Sync_queuees del lenguaje de programación C ++ de Stroustrup: 4ta edición.

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

Esta sencilla aplicación crea el siguiente resultado.

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

La promesa es el otro extremo del cable.

Imagine que necesita recuperar el valor de un futureser calculado por un async. Sin embargo, no desea que se calcule en el mismo hilo, y ni siquiera genera un hilo "ahora"; tal vez su software fue diseñado para elegir un hilo de un grupo, por lo que no sabe quién lo hará realizar che computación al final.

Ahora, ¿qué le pasa a este (todavía desconocido) hilo / clase / entidad? No pasa el future, ya que este es el resultado . Desea pasar algo que está conectado al futurey que representa el otro extremo del cable , por lo que simplemente consultará futuresin saber quién realmente calculará / escribirá algo.

Este es el promise. Es un mango conectado a tu future. Si futurees un altavoz y get()comienza a escuchar hasta que sale algo de sonido, promisees un micrófono ; pero no cualquier micrófono, es el micrófono conectado con un solo cable al altavoz que tiene. Es posible que sepa quién está en el otro extremo, pero no es necesario que lo sepa, solo délo y espere hasta que la otra parte diga algo.

Narcolessico
fuente
0

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

Explicación de una oración: furture :: get () espera promse :: set_value () para siempre.

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
fuente