Concurrence et multithreading en C++ : approches modernes et exemples

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Concurrence et multithreading en C++ : des bases aux normes modernes



Les applications modernes nécessitent de plus en plus souvent l'exécution simultanée de plusieurs tâches. Que ce soit pour le traitement des requêtes utilisateur sur un serveur, le rendu graphique ou le travail avec de grandes quantités de données — la concurrence et le multithreading en C++ sont devenus des compétences obligatoires pour tout développeur. Dans cet article, nous allons passer en revue les mécanismes clés disponibles dans la bibliothèque standard C++, de C++11 à C++20. Vous apprendrez à créer des threads, à synchroniser leur travail et à éviter les erreurs typiques.



Si vous connaissez déjà les bases du C++ mais souhaitez approfondir vos connaissances en parallélisme, ce contenu est pour vous. Nous n'entrerons pas dans la théorie des systèmes d'exploitation, mais nous concentrerons sur les outils pratiques : std::thread, std::mutex, std::async et les opérations atomiques.



1. Bases du multithreading : std::thread et gestion des threads



En C++11, le support standard des threads est apparu via la classe std::thread. Créer un nouveau thread est possible en passant un objet appelable au constructeur — une fonction, une lambda ou un foncteur.



#include <iostream>#include <thread>

void hello() { std::cout << "Bonjour depuis le thread " << std::this_thread::get_id() << std::endl;}

int main() { std::thread t(hello); // Création du thread t.join(); // Attente de la fin du thread return 0;}


Il est important d'appeler join() ou detach() pour chaque thread créé, sinon le programme se terminera brutalement lors de la destruction de l'objet std::thread. join() bloque le thread actuel jusqu'à la fin du thread fils, tandis que detach() permet au thread de fonctionner indépendamment.



Pour passer des arguments au thread, utilisez des paramètres supplémentaires du constructeur :



#include <thread>#include <string>

void print_sum(int a, int b, const std::string& msg) { std::cout << msg << ": " << (a + b) << std::endl;}

int main() { std::thread t(print_sum, 3, 4, "Somme"); t.join(); return 0;}


Notez : les arguments sont copiés à l'intérieur du thread. Si vous devez passer une référence, utilisez std::ref().



2. Synchronisation des threads : mutex et verrous



Lors du travail avec des données partagées depuis différents threads, une course aux données (data race) se produit. Pour l'éviter, on utilise la synchronisation. L'outil principal est std::mutex.



#include <iostream>#include <thread>#include <mutex>

std::mutex mtx;int shared_counter = 0;

void increment() { for (int i = 0; i < 100000; ++i) { mtx.lock(); ++shared_counter; mtx.unlock(); }}

int main() { std::thread t1(increment); std::thread t2(increment); t1.join(); t2.join(); std::cout << "Résultat : " << shared_counter << std::endl; return 0;}


Cependant, l'appel manuel de lock() et unlock() est source d'erreurs : si une exception se produit entre les deux, le mutex reste verrouillé. Il est préférable d'utiliser l'encapsulation RAII std::lock_guard ou std::unique_lock :



void increment() {    for (int i = 0; i < 100000; ++i) {        std::lock_guard<std::mutex> lock(mtx);        ++shared_counter;    }  // se déverrouille automatiquement en sortant de la portée}


Pour une gestion plus flexible (par exemple, un verrouillage conditionnel), utilisez std::unique_lock. Et si vous devez synchroniser plusieurs mutex sans interblocage, appliquez std::lock().



3. Tâches asynchrones : std::async et std::future



Il n'est pas toujours pratique de gérer les threads manuellement.

Blogs

Book Recommendations