¿Cuándo usar la promesa sobre async o packaged_task?

4 minutos de lectura

avatar de usuario
domingos martins

¿Cuándo debo usar std::promise sobre std::async o std::packaged_task? ¿Puedes darme ejemplos prácticos de cuándo usar cada uno de ellos?

avatar de usuario
usuario2622016

std::async

std::async es una manera ordenada y fácil de obtener un std::futurepero:

  • No siempre comienza un nuevo hilo; el valor de enumeración std::launch::async se puede pasar como primer argumento a std::async para garantizar que se crea un nuevo hilo para ejecutar la tarea especificada por funcasegurando así que func se ejecuta de forma asíncrona.

      auto f = std::async( std::launch::async, func );
    
  • los destructor de std::future pueden bloquear hasta que se complete el nuevo hilo.

      auto sleep = [](int s) { std::this_thread::sleep_for(std::chrono::seconds(s)); };
    
      {
          auto f = std::async( std::launch::async, sleep, 5 );
      }
    

Normalmente esperamos que sólo .get() o .wait() bloques, pero por un std::future regresado de std::asyncel destructor también puede bloquear, así que tenga cuidado de no bloquear su subproceso principal simplemente olvidándose de él.

  • Si el std::future se almacena en un objeto temporal, el std::async la llamada se bloqueará en el punto de destrucción del objeto, por lo que el siguiente bloque tomará 10 segundos si quitas el auto f = inicializaciones. Solo se bloqueará para 5 segundos de lo contrario, porque los dos sueños serán concurrentes, con una espera para que ambos se completen como resultado de la destrucción de los dos objetos al final del bloque:

      auto sleep = [](int s) { std::this_thread::sleep_for(std::chrono::seconds(s)); };
    
      {
          auto f1 = std::async( std::launch::async, sleep, 5 );
          auto f2 = std::async( std::launch::async, sleep, 5 );
      }
    

std::packaged_task

std::packaged_task por sí mismo no tiene nada que ver con hilos: es solo un funtor y un relacionado std::future. Considera lo siguiente:

auto task = [](int i) {
   std::this_thread::sleep_for(std::chrono::seconds(5));
   return i+100;
};

std::packaged_task< int(int) > package{ task };
std::future<int> f = package.get_future();
package(1);
std::cout << f.get() << "\n";

Aquí simplemente ejecutamos la tarea por package(1)y después de que regrese, f está listo para que no se bloquee .get().

Hay una característica de std::packaged_task eso lo hace muy útil para hilos. En lugar de solo una función, puede inicializar std::thread con un std::packaged_task lo que brinda una forma realmente agradable de llegar al ‘std::future’. Considera lo siguiente:

std::packaged_task< int(int) > package{ task };
std::future<int> f = package.get_future();
std::thread t { std::move(package), 5 };

std::cout << f.get() << "\n";       //block here until t finishes

t.join();

Porque std::packaged_task no es copiable, debe moverlo a un nuevo hilo con std::move.

std::promesa

std::promise es un mecanismo poderoso. Por ejemplo, puede pasar un valor a un nuevo hilo sin necesidad de ninguna sincronización adicional.

auto task = [](std::future<int> i) {
    std::cout << i.get() << std::flush;
};

std::promise<int> p;
std::thread t{ task, p.get_future() };

std::this_thread::sleep_for(std::chrono::seconds(5));
p.set_value(5);

t.join();

Nuevo hilo nos esperará en .get()


Entonces, en general, respondiendo a tu pregunta:

  • Usar std::async solo para cosas simples, por ejemplo, para hacer alguna llamada sin bloqueo, pero tenga en cuenta los comentarios sobre bloqueo anteriores.

  • Usar std::packaged_task llegar fácilmente a un std::futurey ejecutarlo como un hilo separado

      std::thread{ std::move(package), param }.detach();
    

o

    std::thread t { std::move(package), param };
  • Usar std::promise cuando necesite más control sobre el futuro.

Ver también std::shared_future y al pasar excepciones entre hilos std::promise::set_exception

Una promesa se usa para almacenar un valor que se calculó usando, por ejemplo, un std::async. Ver http://en.cppreference.com/w/cpp/thread/promise

Me imagino que se preguntará acerca de la diferencia entre std::packaged_task y std::async (en el enfoque más común, std::async inicia un hilo separado AHORA para ejecutar function/lambda/etc con un cálculo (probablemente) costoso. A std::packaged_task se usa para envolver una función/lambda/etc con los valores actuales de los argumentos para que pueda ejecutarlos MÁS TARDE, ya sea de forma síncrona o en un subproceso separado).

Tanto std::packaged_task como std::async proporcionan un std::future que contendrá el RESULTADO de la función envuelta/lambda/etc una vez que se ejecute. Internamente, el std::future usa un std::promise para mantener ese resultado.

  • std::future y std::promise tener un “estado compartido”, por lo que el resultado no se almacena en el objeto de promesa, sino en el estado compartido

    – usuario2622016

    10 de junio de 2014 a las 17:37

  • packaged_task no empaqueta valores de argumentos, solo contiene un objeto de función y un “estado compartido”.

    – usuario2622016

    10 de junio de 2014 a las 17:41

¿Ha sido útil esta solución?