Programmation asynchrone en C++ : guide complet pratique
La programmation asynchrone en C++ est une approche qui permet d'exécuter plusieurs tâches en parallèle sans bloquer le thread d'exécution principal. Contrairement au code synchrone où chaque opération attend la fin de la précédente, le code asynchrone peut lancer des opérations longues (entrées-sorties, requêtes réseau, calculs) et continuer l'exécution d'autres tâches pendant que ces opérations se terminent en arrière-plan. Ceci est crucial pour créer des applications réactives, notamment dans l'industrie du jeu, les serveurs web et les systèmes temps réel.
Dans cet article, nous analyserons la programmation asynchrone en C++ en pratique : des mécanismes classiques std::async et std::future aux coroutines modernes C++20. Vous apprendrez à utiliser correctement ces outils, à éviter les erreurs typiques et à créer des systèmes asynchrones efficaces.
1. Fondamentaux de l'asynchrone : std::async et std::future
La bibliothèque standard C++11 fournit un moyen simple de lancer des tâches asynchrones — la fonction std::async et la classe std::future. std::async lance une fonction dans un thread séparé (ou un pool de threads) et retourne un std::future qui contient le résultat de l'exécution.
Exemple 1 : Lancement basique d'une tâche asynchrone
#include <iostream>#include <future>#include <thread>#include <chrono>
int longComputation(int n) { std::this_thread::sleep_for(std::chrono::seconds(2)); return n * n;}
int main() { std::cout << "Lancement de la tâche asynchrone..." << std::endl; std::future<int> result = std::async(std::launch::async, longComputation, 10);
// Pendant que la tâche s'exécute, on peut faire autre travail std::cout << "Le thread principal continue son travail..." << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1));
// Récupération du résultat (bloque le thread si la tâche n'est pas encore terminée) int value = result.get(); std::cout << "Résultat : " << value << std::endl; return 0;}Points importants :
std::launch::asyncgarantit le lancement dans un thread séparé.result.get()bloque le thread actuel jusqu'à l'obtention du résultat.- Si
get()n'est pas appelé, le destructeur destd::futurepeut bloquer l'exécution jusqu'à la fin de la tâche.
Exemple 2 : Gestion des exceptions dans les tâches asynchrones
#include <iostream>#include <future>#include <stdexcept>
int riskyFunction(int x) { if (x < 0) throw std::runtime_error("Nombre négatif !"); return x * 2;}
int main() { std::future<int> future = std::async(std::launch::async, riskyFunction, -5); try { int result = future.get(); std::cout << "Résultat : " << result << std::endl; } catch (const std::exception& e) { std::cout << "Exception : " << e.what() << std::endl; } return 0;}2. Travail avancé avec std::future et std::promise
std::promise est un outil pour la gestion manuelle des résultats asynchrones. Vous pouvez définir une valeur ou une exception dans un thread, et la récupérer via std::future dans un autre. Cela est utile pour implémenter vos propres modèles asynchrones, par exemple pour transférer des données entre threads.
Exemple 3 : Utilisation de std::promise pour le transfert de données
#include <iostream>#include <future>#include <thread>
void producer(std::promise<std::string> prom) { std::this_thread::sleep_for(std::chrono::seconds(1)); prom.set_value("Données prêtes !");}
int main() { std::promise<std::string> promise; std::future<s