Concurrence et multithreading en C++ : niveau avancé

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 techniques avancées



Les applications modernes exigent de plus en plus de performances maximales, en particulier lorsqu'elles travaillent avec des processeurs multicœurs. C++ offre des outils puissants pour la concurrence et le multithreading, depuis C++11 jusqu'aux derniers standards. Dans cet article, nous aborderons les aspects avancés du multithreading : de l'utilisation correcte des mutex et des opérations atomiques aux modèles de synchronisation complexes. Le contenu est destiné aux développeurs déjà familiarisés avec les bases des threads en C++.



1. Gestion des threads : std::thread et std::jthread



La classe std::thread est la base de la programmation multithread en C++. Cependant, à un niveau avancé, il est important non seulement de créer un thread, mais aussi de gérer correctement son cycle de vie. Avec C++20, std::jthread est apparu, qui se joint automatiquement lors de la destruction, évitant ainsi les fuites de ressources.



Exemple d'utilisation de std::jthread :



#include <iostream>#include <thread>#include <chrono>

void worker(int id) { std::this_thread::sleep_for(std::chrono::milliseconds(100 * id)); std::cout << "Thread " << id << " terminé";}

int main() { // std::jthread se joint automatiquement à la sortie de la portée std::jthread t1(worker, 1); std::jthread t2(worker, 2); std::jthread t3(worker, 3); // Pas besoin d'appeler explicitement join() ou detach() // Lors de la destruction de t1, t2, t3, main attend leur fin std::cout << "Le thread principal continue son travail"; return 0;}


Il est important de se rappeler : std::jthread prend également en charge le mécanisme d'arrêt via std::stop_token, ce qui permet de terminer en toute sécurité les tâches d'arrière-plan longues.



2. Synchronisation d'accès : mutex, verrous et variables conditionnelles



Le principal problème du multithreading est les courses de données. Pour les éviter, on utilise des mutex. À un niveau avancé, il est recommandé d'utiliser std::scoped_lock (C++17) pour verrouiller plusieurs mutex sans risque d'interblocage.



Exemple avec std::scoped_lock :



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

std::mutex mtx1, mtx2;int shared_data = 0;

void safe_increment(int id) { // scoped_lock verrouille les deux mutex dans l'ordre, évitant le deadlock std::scoped_lock lock(mtx1, mtx2); ++shared_data; std::cout << "Thread " << id << " a augmenté la valeur à " << shared_data << "";}

int main() { std::vector<std::jthread> threads; for (int i = 0; i < 10; ++i) { threads.emplace_back(safe_increment, i); } return 0;}


Pour des scénarios de synchronisation plus complexes (par exemple, producteur-consommateur), on utilise des variables conditionnelles (std::condition_variable). Elles permettent aux threads d'attendre qu'un certain événement se produise, sans gaspiller de ressources en attente active.



3. Opérations atomiques et modèle mémoire C++



Les opérations atomiques (std::atomic) sont la base de la programmation sans verrou (lock-free). Elles garantissent que la lecture et l'écriture d'une variable sont effectuées comme une seule action indivisible. Cependant, à un niveau avancé, il est important de comprendre le modèle mémoire C++ et les niveaux d'ordonnancement (memory ordering).



Principaux niveaux de memory ordering :

  • memory_order_relaxed — garanties minimales, atomicité uniquement
  • memory_order_acquire — garantit que toutes les lectures et écritures ultérieures verront les résultats des opérations précédentes
  • memory_order_release — garantit que toutes les opérations précédentes seront visibles après cette écriture
  • memory_order_acq_rel — combinaison d'acquire et de release

Blogs

Book Recommendations