Futures, Promises y Tareas Asincronas
Usa std::future, std::promise y std::async para comunicacion de valores entre hilos. Comprende las politicas de lanzamiento, shared futures y la propagacion de excepciones a traves de limites de hilo.
El Patron Future-Promise
El patron future-promise proporciona un canal seguro entre hilos para transferir un unico valor. Un std::promise es el extremo del productor — estableces el valor (o excepcion). Un std::future es el extremo del consumidor — esperas y recuperas el resultado.
Este patron desacopla la produccion del consumo: al productor no le importa cuando el consumidor lee, y al consumidor no le importa cuando el productor escribe. La sincronizacion esta incorporada.
std::async simplifica este patron lanzando una tarea (funcion) y devolviendo un future que contendra el resultado cuando la tarea complete. Es la forma mas facil de hacer trabajo en segundo plano en C++.
std::async y Politicas de Lanzamiento
std::async lanza una tarea y devuelve un std::future. La politica de lanzamiento controla si la tarea se ejecuta en un nuevo hilo o diferida hasta que se llame a .get().
#include <iostream>
#include <future>
#include <chrono>
#include <numeric>
#include <vector>
long long parallel_sum(const std::vector<int>& v, size_t start, size_t end) {
if (end - start < 1000) {
return std::accumulate(v.begin() + start, v.begin() + end, 0LL);
}
size_t mid = start + (end - start) / 2;
// Launch left half asynchronously
auto left = std::async(std::launch::async,
parallel_sum, std::cref(v), start, mid);
// Compute right half in this thread
long long right_sum = parallel_sum(v, mid, end);
return left.get() + right_sum; // .get() blocks until left is done
}
int main() {
std::vector<int> data(1'000'000, 1);
auto start = std::chrono::high_resolution_clock::now();
long long result = parallel_sum(data, 0, data.size());
auto end = std::chrono::high_resolution_clock::now();
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
std::cout << "Sum: " << result << " in " << ms.count() << "ms\n";
return 0;
}std::promise — Produccion Manual de Valores
std::promise te da control manual sobre cuando y como se establece un valor para un future. Es util cuando la creacion de hilos esta separada del sitio de produccion de valores.
Propagacion de Excepciones a Traves de Limites de Hilo
Las excepciones no pueden cruzar limites de hilo directamente. Pero std::future proporciona una solucion elegante: si la tarea lanza una excepcion, la excepcion se almacena en el estado compartido del future. Cuando el consumidor llama a .get(), la excepcion se relanza en el hilo del consumidor.
Esto funciona tanto con std::async como con std::promise:
auto f = std::async([] {
throw std::runtime_error("oops");
return 42;
});
try {
int val = f.get(); // relanza la excepcion almacenada
} catch (const std::runtime_error& e) {
// Manejado en el hilo del llamador
}Tambien puedes establecer excepciones manualmente con promise.set_exception(std::make_exception_ptr(e)).
std::shared_future — Multiples Consumidores
std::shared_future permite que multiples hilos esperen y lean el mismo resultado. A diferencia de std::future (de movimiento unico), shared_future es copiable y multiples hilos pueden llamar a .get() concurrentemente.
#include <iostream>
#include <future>
#include <thread>
#include <vector>
int main() {
auto shared = std::async(std::launch::async, []() {
std::this_thread::sleep_for(std::chrono::seconds(1));
return 42;
}).share(); // convert future to shared_future
// Multiple threads can all read the same result
std::vector<std::thread> consumers;
for (int i = 0; i < 5; ++i) {
consumers.emplace_back([shared, i]() {
int val = shared.get(); // all 5 threads get 42
std::cout << "Consumer " << i << " got: " << val << "\n";
});
}
for (auto& t : consumers) t.join();
return 0;
}Un std::future devuelto por std::async tiene una propiedad especial: su destructor se bloquea hasta que la tarea se completa. Esto significa que si descartas el future, la llamada se vuelve efectivamente sincrona:
std::async(std::launch::async, expensive_task); // Se bloquea aqui!
// El future temporal se destruye inmediatamente, esperando a que la tarea termineSiempre asigna el resultado de std::async a una variable para mantener el future vivo hasta que realmente quieras esperar.
Este comportamiento de destructor bloqueante es unico de los futures devueltos por std::async — los futures de std::promise o std::packaged_task no se bloquean en la destruccion.
Elige la herramienta correcta para el trabajo:
std::async — La opcion mas simple. Usalo para tareas de dispara-y-olvida con retorno de resultado. La implementacion gestiona los hilos por ti.
std::promise + std::future — Usalo cuando la creacion de hilos este separada de la produccion de valores, o cuando necesites control manual sobre cuando y como se entrega el resultado.
std::thread crudo — Usalo cuando necesites hilos de larga vida, control del pool de hilos, afinidad de CPU u opciones avanzadas de planificacion que async no proporciona.
Para tareas de solo fuego que no necesitan un resultado, un pool de hilos con una cola de tareas es a menudo mas eficiente que lanzar un nuevo hilo por tarea.
Use std::async when you want a result from concurrent work and don't need fine-grained thread control. It automatically propagates exceptions and manages thread lifetimes. Use std::thread (or std::jthread) when you need persistent background workers, custom scheduling, or when you're building infrastructure like thread pools. For fire-and-forget work, always keep the returned std::future alive to avoid accidental blocking.
std::asynclaunches a task and returns astd::future— a high-level alternative to manual thread managementstd::launch::asyncforces a new thread;std::launch::deferreddelays untilget()is calledstd::promiseis the write end of a future channel — use it for manual value/exception settingstd::shared_futurelets multiple threads read the same result- The
std::futurefromstd::asyncblocks in its destructor — always store it in a variable std::packaged_taskwraps a callable with a future, ideal for custom thread pools
Quiz — Pon a prueba tus conocimientos
(15 XP)1. Cual es la diferencia entre `std::launch::async` y `std::launch::deferred`?
2. Cual es la diferencia clave entre `std::future` y `std::shared_future`?
3. Como propaga `std::async` excepciones de la tarea al llamador?