Programación asíncrona en C++: de std::async a las corrutinas
La programación asíncrona en C++ es una herramienta poderosa para crear aplicaciones de alto rendimiento y receptivas. En el mundo actual, donde los procesadores multinúcleo se han convertido en el estándar, saber gestionar eficientemente tareas paralelas es una habilidad obligatoria para todo desarrollador de C++. En este artículo, analizaremos los conceptos clave, comenzando con los mecanismos básicos de la biblioteca estándar y terminando con las corrutinas modernas de C++20.
¿Por qué es necesaria la programación asíncrona?
El objetivo principal de la programación asíncrona es no bloquear el hilo principal de ejecución del programa. Imagine que su aplicación debe descargar un archivo de la red, procesarlo y mostrarlo al usuario. Si realiza estas operaciones de forma síncrona, la interfaz se "congelará" durante la descarga. El enfoque asíncrono permite ejecutar una operación larga "en segundo plano", mientras que el hilo principal continúa procesando la entrada del usuario.
Enfoques principales para la asincronía en C++
En C++ existen varias formas de implementar la ejecución asíncrona:
- Gestión manual de hilos (std::thread) — un enfoque de bajo nivel, pero flexible.
- std::async y std::future — un mecanismo de alto nivel para lanzar tareas asíncronas.
- std::packaged_task y std::promise — para un control más preciso.
- Corrutinas (C++20) — una forma moderna y elegante de escribir código asíncrono.
Uso de std::async y std::future
Esta es la forma más sencilla de lanzar una tarea asíncrona. La función std::async devuelve un objeto std::future que contendrá el resultado de la ejecución.
#include <iostream>#include <future>#include <thread>#include <chrono>
int heavy_computation(int x) { std::this_thread::sleep_for(std::chrono::seconds(2)); return x * x;}
int main() { std::cout << "Lanzando tarea asíncrona..."; std::future<int> result = std::async(std::launch::async, heavy_computation, 42); // Hacemos algo más mientras la tarea se ejecuta std::cout << "¡El hilo principal no está bloqueado!"; // Obtenemos el resultado (se bloquea si la tarea aún no ha terminado) int value = result.get(); std::cout << "Resultado: " << value << ""; return 0;}La bandera std::launch::async garantiza que la tarea se ejecutará en un hilo separado. Si no se especifica, la implementación puede retrasar la ejecución hasta que se llame a .get().
Trabajo con std::promise y std::packaged_task
Estos mecanismos proporcionan más control. std::promise permite "prometer" un valor que se establecerá más tarde, mientras que std::packaged_task envuelve un objeto invocable en una tarea.
#include <iostream>#include <future>#include <thread>
void set_value(std::promise<int>&& prom) { std::this_thread::sleep_for(std::chrono::seconds(1)); prom.set_value(100);}
int main() { std::promise<int> prom; std::future<int> fut = prom.get_future(); std::thread t(set_value, std::move(prom)); std::cout << "Esperando resultado..."; int value = fut.get(); std::cout << "Recibido: " << value << ""; t.join(); return 0;}Importante: std::promise y std::future son objetos de solo movimiento. No se pueden copiar, solo mover.
Corrutinas en C++20: un nuevo nivel de asincronía
Las corrutinas (coroutines) son funciones que pueden pausar su ejecución y reanudarla más tarde. Permiten escribir código asíncrono como si fuera síncrono.