c++ - create - Confusión sobre los hilos lanzados por std:: async con el parámetro std:: launch:: async
create thread c++ (3)
Eso no es realmente cierto. Agregue el valor almacenado thread_local
y verá que en realidad std::async run f1 f2 f3
tareas en diferentes subprocesos, pero con el mismo std::thread::id
Estoy un poco confundido acerca de la función std::async
.
La especificación dice: la operación asíncrona se está ejecutando "como en un nuevo hilo de ejecución" (C ++ 11 §30.6.8 / 11).
Ahora, ¿qué se supone que significa eso?
En mi entendimiento, el código
std::future<double> fut = std::async(std::launch::async, pow2, num);
debe iniciar la función pow2
en un nuevo hilo y pasar la variable num
al hilo por valor, y luego, en algún momento en el futuro, cuando la función haya terminado, coloque el resultado en fut
(siempre que la función pow2
tenga una firma como double pow2(double);
). Pero la especificación dice "como si", lo que hace que todo sea un poco confuso para mí.
La pregunta es:
¿Se lanza siempre un nuevo hilo en este caso? Yo espero que sí. En mi opinión, el parámetro std::launch::async
tiene sentido de una manera en la que estoy declarando explícitamente que deseo crear un nuevo hilo.
Y el codigo
std::future<double> fut = std::async(std::launch::deferred, pow2, num);
debería hacer posible la evaluación perezosa , retrasando la llamada de la función pow2
al punto donde escribo algo como var = fut.get();
. En este caso, el parámetro std::launch::deferred
debería significar que estoy declarando explícitamente que no quiero un nuevo hilo, solo quiero asegurarme de que se llame a la función cuando sea necesario para su valor de retorno.
¿Mis suposiciones son correctas? Si no, por favor explique.
Además, sé que, por defecto, la función se llama de la siguiente manera:
std::future<double> fut = std::async(std::launch::deferred | std::launch::async, pow2, num);
En este caso, me dijeron que si un nuevo hilo se lanzará o no depende de la implementación. De nuevo, ¿qué se supone que significa eso?
Esto también me confundió y ejecuté una prueba rápida en Windows que muestra que el futuro asíncrono se ejecutará en los subprocesos del grupo de subprocesos del sistema operativo. Una aplicación simple puede demostrar esto, al descomponerse en Visual Studio también se mostrarán los subprocesos en ejecución llamados "TppWorkerThread".
#include <future>
#include <thread>
#include <iostream>
using namespace std;
int main()
{
cout << "main thread id " << this_thread::get_id() << endl;
future<int> f1 = async(launch::async, [](){
cout << "future run on thread " << this_thread::get_id() << endl;
return 1;
});
f1.get();
future<int> f2 = async(launch::async, [](){
cout << "future run on thread " << this_thread::get_id() << endl;
return 1;
});
f2.get();
future<int> f3 = async(launch::async, [](){
cout << "future run on thread " << this_thread::get_id() << endl;
return 1;
});
f3.get();
cin.ignore();
return 0;
}
Resultará en una salida similar a:
main thread id 4164
future run on thread 4188
future run on thread 4188
future run on thread 4188
La plantilla de función std::async
(parte de la cabecera <future>
) se utiliza para iniciar una tarea asíncrona (posiblemente). Devuelve un objeto std::future
, que eventualmente retendrá el valor de retorno de la función de parámetros de std::async
.
Cuando se necesita el valor, llamamos a get () en la instancia std::future
; esto bloquea el hilo hasta que el futuro esté listo y luego devuelve el valor. std::launch::async
o std::launch::deferred
se puede especificar como el primer parámetro de std::async
para especificar cómo se ejecuta la tarea.
-
std::launch::async
indica que la llamada a la función debe ejecutarse en su propio (nuevo) hilo. (Tome en cuenta el comentario del usuario @ TC). -
std::launch::deferred
indica que la llamada a la función se aplazará hasta que se llame await()
oget()
en el futuro. La propiedad del futuro se puede transferir a otro hilo antes de que esto suceda. -
std::launch::async | std::launch::deferred
std::launch::async | std::launch::deferred
indica que la implementación puede elegir. Esta es la opción predeterminada (cuando no especifica uno por sí mismo). Puede decidir ejecutarse sincrónicamente.
¿Se lanza siempre un nuevo hilo en este caso?
Desde 1. podemos decir que siempre se lanza un nuevo hilo.
¿Son correctas mis suposiciones [en std :: launch :: deferred]?
A partir del 2. , podemos decir que sus suposiciones son correctas.
¿Qué se supone que significa eso? [en relación con un nuevo hilo que se está lanzando o no dependiendo de la implementación]
Desde 3. , como std::launch::async | std::launch::deferred
std::launch::async | std::launch::deferred
es la opción por defecto, significa que la implementación de la función de plantilla std::async
decidirá si creará un nuevo hilo o no. Esto se debe a que algunas implementaciones pueden estar comprobando la programación excesiva.
ADVERTENCIA
La siguiente sección no está relacionada con su pregunta, pero creo que es importante tener en cuenta.
El estándar de C ++ dice que si un std::future
contiene la última referencia al estado compartido correspondiente a una llamada a una función asíncrona, el destructor de std :: future debe bloquearse hasta que finalice el subproceso de la función en ejecución asíncrona. Una instancia de std::future
devuelta por std::async
se bloqueará en su destructor.
void operation()
{
auto func = [] { std::this_thread::sleep_for( std::chrono::seconds( 2 ) ); };
std::async( std::launch::async, func );
std::async( std::launch::async, func );
std::future<void> f{ std::async( std::launch::async, func ) };
}
Este código engañoso puede hacerte pensar que las llamadas std::async
son asíncronas, en realidad son síncronas. Las instancias std::future
devueltas por std::async
son temporales y se bloquearán porque su destructor se llama justo cuando std::async
regresa ya que no están asignadas a una variable.
La primera llamada a std::async
se bloqueará durante 2 segundos, seguidos de otros 2 segundos de bloqueo desde la segunda llamada a std::async
. Podemos pensar que la última llamada a std::async
no se bloquea, ya que almacenamos su instancia std::future
devuelta en una variable, pero como esa es una variable local que se destruye al final del alcance, en realidad bloque durante 2 segundos adicionales al final del alcance de la función, cuando se destruye la variable local f.
En otras palabras, llamar a la función operation()
bloqueará cualquier subproceso en el que se llame de forma síncrona durante aproximadamente 6 segundos. Es posible que tales requisitos no existan en una versión futura del estándar C ++.
Fuentes de información que utilicé para compilar estas notas:
C ++ Concurrencia en Acción: Multihilo práctico, Anthony Williams
Publicación del blog de Scott Meyers: http://scottmeyers.blogspot.ca/2013/03/stdfutures-from-stdasync-arent-special.html