Introduction à la programmation asynchrone en C++
Les applications modernes exigent de plus en plus de hautes performances et de réactivité. La programmation asynchrone en C++ est un outil puissant qui permet d'exécuter plusieurs tâches simultanément sans bloquer le thread principal. Dans ce guide, nous allons explorer les bases de l'asynchrone en C++ : des approches classiques aux coroutines modernes (coroutines), ajoutées dans la norme C++20.
Si vous êtes débutant en C++ mais déjà familier avec la syntaxe de base, cet article vous aidera à comprendre comment faire faire plusieurs choses à la fois à votre programme. Nous examinerons des exemples pratiques que vous pouvez immédiatement appliquer dans vos projets.
Concepts fondamentaux et pourquoi c'est nécessaire
Dans la programmation synchrone traditionnelle, le code s'exécute strictement séquentiellement : une opération se termine, puis la suivante commence. C'est simple, mais inefficace si le programme attend des entrées-sorties (lecture de fichier, requête réseau) ou effectue des calculs lourds.
La programmation asynchrone résout ce problème en permettant :
- De ne pas bloquer le thread principal — l'interface de l'application reste réactive.
- D'accélérer l'exécution — traitement parallèle des données sur les processeurs multicœurs.
- D'utiliser efficacement les ressources — pendant qu'une tâche attend des données, une autre peut s'exécuter.
En C++, il existe plusieurs façons d'implémenter l'asynchrone : des threads de bas niveau (std::thread) aux abstractions de haut niveau (std::async, std::future et les coroutines).
1. Asynchrone avec std::async et std::future
La façon la plus simple de lancer une tâche asynchrone en C++ est d'utiliser la fonction std::async du fichier d'en-tête <future>. Elle retourne un objet std::future qui stocke le résultat de l'exécution.
Exemple de base
#include <iostream>#include <future>#include <thread>#include <chrono>
// Fonction à exécuter de manière asynchroneint slow_calculation(int x) { std::this_thread::sleep_for(std::chrono::seconds(2)); // Simulation d'un travail long return x * x;}
int main() { std::cout << "Lancement de la tâche asynchrone..." << std::endl; // Lancement de slow_calculation dans un thread séparé std::future<int> result = std::async(std::launch::async, slow_calculation, 5); std::cout << "Le thread principal continue son travail..." << std::endl; // 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; // Affichage : 25 return 0;}Dans cet exemple, std::async lance la fonction dans un thread séparé. Pendant son exécution, le thread principal affiche un message. La méthode get() attend la fin de la tâche et retourne le résultat.
Politiques de lancement
std::async a deux modes :
std::launch::async— la tâche est garantie d'être exécutée dans un nouveau thread.std::launch::deferred— la tâche est exécutée paresseusement, uniquement lors de l'appel àget()ouwait()dans le même thread.
2. Promesses et Futures : gestion manuelle
Si vous avez besoin d'une gestion plus flexible des tâches asynchrones, utilisez la paire std::promise et std::future. promise permet de définir une valeur ou une exception dans un thread, et future permet de la récupérer dans un autre.
Exemple avec promise
#include <iostream>#include <future>#include <thread>
void worker(std::promise<int> prom, int a, int b) { try { int result = a / b; // Peut