std::async
| Definido en el archivo de encabezado <future>
|
||
| (1) | ||
template< class Function, class... Args> std::future<std::result_of_t<std::decay_t<Function>(std::decay_t<Args>...)>> async( Function&& f, Args&&... args ); |
(desde C++11) (hasta C++17) |
|
template< class Function, class... Args> std::future<std::invoke_result_t<std::decay_t<Function>, std::decay_t<Args>...>> async( Function&& f, Args&&... args ); |
(desde C++17) (hasta C++20) |
|
template< class Function, class... Args> [[nodiscard]] std::future<std::invoke_result_t<std::decay_t<Function>, std::decay_t<Args>...>> async( Function&& f, Args&&... args ); |
(desde C++20) | |
| (2) | ||
template< class Function, class... Args > std::future<std::result_of_t<std::decay_t<Function>(std::decay_t<Args>...)>> async( std::launch policy, Function&& f, Args&&... args ); |
(desde C++11) (hasta C++17) |
|
template< class Function, class... Args > std::future<std::invoke_result_t<std::decay_t<Function>, std::decay_t<Args>...>> async( std::launch policy, Function&& f, Args&&... args ); |
(desde C++17) (hasta C++20) |
|
template< class Function, class... Args > [[nodiscard]] std::future<std::invoke_result_t<std::decay_t<Function>, std::decay_t<Args>...>> async( std::launch policy, Function&& f, Args&&... args ); |
(desde C++20) | |
La plantilla de función async ejecuta la función f asíncronamente (potencialmente en un hilo separado que puede ser parte de una reserva de hilos) y devuelve un std::future que eventualmente contendrá el resultado de esa llamada a función.
policy siendo std::launch::async | std::launch::deferred. En otras palabras, f puede ejecutarse en otro hilo o puede ejecutarse asíncronamente cuando el std::future resultante es consultado por un valor.f con los argumentos args de acuerdo con una política de lanzamiento específica policy:
- Si la bandera async se establece (es decir,
policy & std::launch::async != 0), entoncesasyncejecuta el objeto llamablefen un nuevo hilo de ejecución (con todas las variables locales al hilo inicializadas) como si se hubieran generado porstd::thread(std::forward<F>(f), std::forward<Args>(args)...), excepto que si la funciónfdevuelve un valor o produce una excepción, éste se almacena en el estado compartido accesible a través del std::future queasyncdevuelve al llamador. - Si la bandera deferred se establece (es decir,
policy & std::launch::deferred != 0), entoncesasyncconvierte afy aargs...de la misma manera que si lo fuera mediante el constructor de std::thread, pero no genera un nuevo hilo de ejecución. En su lugar se realiza evaluación perezosa: la primera llamada a una función de espera sin sincronizar en el std::future que devolvióasyncal llamador ocasionará que se invoque a la copia def(como un rvalue) con las copias deargs...(también pasados como rvalues) en el hilo actual (que no tiene que ser el hilo que llamó originalmente astd::async). El resultado o excepción se ubica en el estado compartido asociado con el std::future y solamente entonces se deja listo. Todos los accesos posteriores al mismo std::future devolverán el resultado inmediatamente.
- Si tanto la bandera std::launch::async como la bandera std::launch::deferred están establecidas en
policy, depende de la implementación si se realiza una ejecución asíncrona o una evaluación perezosa.
- Si tanto la bandera std::launch::async como la bandera std::launch::deferred están establecidas en
|
(desde C++14) |
En cualquier caso, la llamada a std::async se sincroniza-con (como se define en el orden de memoria (std::memory_order)) la llamada a f, y la finalización de f es secuenciada-antes de dejar el estado compartido listo. Si se escoge la política async, la finalización del hilo asociado se sincroniza-con el retorno exitoso de la primera función que está esperando al estado compartido, o con el retorno de la última función que libera el estado compartido, lo que suceda primero.
Parámetros
| f | - | Objeto que puede ser llamado (Callable) a llamar. | ||||||
| args... | - | Parámetros para pasar a f.
| ||||||
| policy | - | Valor de máscara de bits, donde los bits individuales controlan los métodos de ejecución permitidos.
| ||||||
| Requisitos de tipo | ||||||||
-Function, Args debe satisfacer los requisitos de MoveConstructible.
| ||||||||
Valor de retorno
Un futuro (std::future) que se refiere al estado compartido creado por esta llamada a std::async.
Excepciones
Lanza std::system_error con la condición de error std::errc::resource_unavailable_try_again si la política de lanzamiento es igual a std::launch::async y la implementación no es capaz de iniciar un nuevo hilo (si la política es async|deferred o tiene bits adicionales establecidos, volverá a deferred o a las políticas definidas por la implementación, en este caso), o std::bad_alloc si no se puede asignar memoria para las estructuras de datos internas.
Notas
La implementación puede extender el comportamiento de la primera sobrecarga de std::async al habilitar bits adicionales (definidos por la implementación) en la política de inicio predeterminada.
Ejemplos de políticas de inicio definidas por la implementación son la política de sincronización (ejecutar inmediatamente, dentro de la llamada asíncrona) y la política de tareas (similar a la asíncrona, pero las variables locales al hilo no se borran).
Si el futuro (std::future) obtenido de std::async no se mueve o se vincula a una referencia, el destructor del std::future se bloqueará al final de la expresión completa hasta que se complete la operación asíncrona, esencialmente haciendo que código como el siguiente sea síncrono:
std::async(std::launch::async, []{ f(); }); // dtor del temporal espera a f()
std::async(std::launch::async, []{ g(); }); // no comienza hasta que se complete f()
(ten en cuenta que los destructores de futuros std::future obtenidos por medios distintos de una llamada a std::async nunca se bloquean).
Ejemplo
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
#include <future>
#include <string>
#include <mutex>
std::mutex m;
struct X {
void foo(int i, const std::string& str) {
std::lock_guard<std::mutex> lk(m);
std::cout << str << ' ' << i << '\n';
}
void bar(const std::string& str) {
std::lock_guard<std::mutex> lk(m);
std::cout << str << '\n';
}
int operator()(int i) {
std::lock_guard<std::mutex> lk(m);
std::cout << i << '\n';
return i + 10;
}
};
template <typename RandomIt>
int parallel_sum(RandomIt beg, RandomIt end)
{
auto len = end - beg;
if (len < 1000)
return std::accumulate(beg, end, 0);
RandomIt mid = beg + len/2;
auto handle = std::async(std::launch::async,
parallel_sum<RandomIt>, mid, end);
int sum = parallel_sum(beg, mid);
return sum + handle.get();
}
int main()
{
std::vector<int> v(10000, 1);
std::cout << "La suma es " << parallel_sum(v.begin(), v.end()) << '\n';
X x;
// Llama a (&x)->foo(42, "Hola") con la política por defecto:
// puede imprimir "Hola 42" concurrentemente o diferir la ejecución
auto a1 = std::async(&X::foo, &x, 42, "Hola");
// Llama a x.bar("mundo") con la política diferida
// imprime "mundo" cuando se llama a a2.get() o a a2.wait()
auto a2 = std::async(std::launch::deferred, &X::bar, x, "mundo");
// Llama a X()(43); con política asíncrona (async)
// imprime "43" concurrentemente
auto a3 = std::async(std::launch::async, X(), 43);
a2.wait(); // imprime "mundo"
std::cout << a3.get() << '\n'; // imprime "53"
} // si a1 no ha terminado en este punto, el destructor de a1 aquí imprime "Hola 42"
Posible salida:
La suma es 10000
43
mundo
53
Hola 42
Informes de defectos
Los siguientes informes de defectos de cambio de comportamiento se aplicaron de manera retroactiva a los estándares de C++ publicados anteriormente.
| ID | Aplicado a | Comportamiento según lo publicado | Comportamiento correcto |
|---|---|---|---|
| LWG 2021 | C++11 | Tipo de retorno incorrecto y categoría de valor de los argumentos no son claros en el caso diferido |
Tipo de retorno corregido y se clarificó que se utilizan rvalues. |