c++ - ¿Cuál es la diferencia entre packaged_task y async?
multithreading c++11 (3)
Mientras trabajaba con el modelo de C ++ 11, vi que
std::packaged_task<int(int,int)> task([](int a, int b) { return a + b; });
auto f = task.get_future();
task(2,3);
std::cout << f.get() << ''/n'';
y
auto f = std::async(std::launch::async,
[](int a, int b) { return a + b; }, 2, 3);
std::cout << f.get() << ''/n'';
parece hacer exactamente lo mismo. Entiendo que podría haber una gran diferencia si ejecutara std::async
con std::launch::deferred
, pero ¿hay alguno en este caso?
¿Cuál es la diferencia entre estos dos enfoques, y más importante, en qué casos de uso debo usar uno sobre el otro?
"La plantilla de clase std :: packaged_task ajusta cualquier destino invocable (función, expresión lambda, expresión de enlace u otro objeto de función) para que se pueda invocar de forma asíncrona. Su valor de retorno o excepción arrojada se almacena en un estado compartido al que se puede acceder a través de std :: objetos futuros ".
"La función de plantilla async ejecuta la función f de forma asíncrona (potencialmente en un subproceso separado) y devuelve un std :: future que eventualmente contendrá el resultado de esa llamada de función."
Tarea empaquetada vs asincrónica
La tarea empaquetada contiene una tarea [function or function object]
y un par futuro / promesa. Cuando la tarea ejecuta una declaración return, causa set_value(..)
en la promesa de set_value(..)
.
a> Dada la tarea Future, promise y package, podemos crear tareas simples sin preocuparnos demasiado por los hilos [thread es solo algo que damos para ejecutar una tarea].
Sin embargo, debemos considerar cuántos hilos usar o si una tarea se ejecuta mejor en el hilo actual o en otro, etc. Dichas decisiones pueden ser manejadas por un iniciador de subprocesos llamado async()
, que decide si crear un nuevo hilo o recicla uno viejo o simplemente ejecuta la tarea en el hilo actual. Devuelve un futuro.
En realidad, el ejemplo que acaba de mostrar muestra las diferencias si usa una función bastante larga, como
//! sleeps for one second and returns 1
auto sleep = [](){
std::this_thread::sleep_for(std::chrono::seconds(1));
return 1;
};
Tarea empaquetada
Una packaged_task
no se iniciará por su cuenta, debe invocarla:
std::packaged_task<int()> task(sleep);
auto f = task.get_future();
task(); // invoke the function
// You have to wait until task returns. Since task calls sleep
// you will have to wait at least 1 second.
std::cout << "You can see this after 1 second/n";
// However, f.get() will be available, since task has already finished.
std::cout << f.get() << std::endl;
std::async
Por otro lado, std::async
con launch::async
intentará ejecutar la tarea en un hilo diferente:
auto f = std::async(std::launch::async, sleep);
std::cout << "You can see this immediately!/n";
// However, the value of the future will be available after sleep has finished
// so f.get() can block up to 1 second.
std::cout << f.get() << "This will be shown after a second!/n";
Retirarse
Pero antes de intentar utilizar async
para todo, tenga en cuenta que el futuro devuelto tiene un estado compartido especial, que exige que future::~future
bloques future::~future
:
std::async(do_work1); // ~future blocks
std::async(do_work2); // ~future blocks
/* output: (assuming that do_work* log their progress)
do_work1() started;
do_work1() stopped;
do_work2() started;
do_work2() stopped;
*/
Entonces, si desea una asincronía real, debe conservar el future
devuelto, o si no le importa el resultado si las circunstancias cambian:
{
auto pizza = std::async(get_pizza);
/* ... */
if(need_to_go)
return; // ~future will block
else
eat(pizza.get());
}
Para obtener más información al respecto, consulte el artículo async
y ~future
Herb Sutter, que describe el problema, y std::futures
de Scott Meyer de std::async
no son especiales , que describen las ideas. También tenga en cuenta que este comportamiento se especificó en C ++ 14 y posteriores , pero también se implementa comúnmente en C ++ 11.
Otras diferencias
Al usar std::async
, ya no puede ejecutar su tarea en un hilo específico, donde std::packaged_task
se puede mover a otros hilos.
std::packaged_task<int(int,int)> task(...);
auto f = task.get_future();
std::thread myThread(std::move(task),2,3);
std::cout << f.get() << "/n";
Además, se debe invocar una f.get()
antes de llamar a f.get()
; de lo contrario, el programa se congelará ya que el futuro nunca estará listo:
std::packaged_task<int(int,int)> task(...);
auto f = task.get_future();
std::cout << f.get() << "/n"; // oops!
task(2,3);
TL; DR
Use std::async
si desea hacer algunas cosas y realmente no le importa cuando terminen, y std::packaged_task
si desea concluir las cosas para moverlas a otros hilos o llamarlas más tarde. O, para citar a Christian :
Al final,
std::packaged_task
es solo una característica de nivel inferior para implementarstd::async
(que es por lo que puede hacer más questd::async
si se usa junto con otras cosas de nivel inferior, comostd::thread
). Simplemente hablado,std::packaged_task
es unastd::function
vinculada astd::future
ystd::async
envuelve y llama astd::packaged_task
(posiblemente en un hilo diferente).