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++ : niveau avancé

\n\n

Les applications modernes exigent des performances maximales, et le multithreading en C++ est l'un des outils clés pour y parvenir. Cependant, les mécanismes de base (std::thread, std::mutex) entraînent souvent des blocages, des conditions de course et une perte d'efficacité. Dans cet article, nous explorerons les techniques avancées de gestion de la concurrence en C++ : des opérations atomiques aux structures de données lock-free et aux tâches asynchrones.

\n\n

1. Opérations atomiques et memory ordering

\n\n

Les mutex classiques sont un mécanisme lourd. Pour des compteurs ou des drapeaux simples, il est préférable d'utiliser std::atomic. Il garantit l'atomicité des lectures/écritures sans verrouillage. Cependant, à un niveau avancé, il est crucial de comprendre le memory ordering — le modèle mémoire du C++ qui détermine la visibilité des modifications entre les threads.

\n\n

Les principaux ordres mémoire :

\n
    \n
  • memory_order_relaxed — garanties minimales, atomicité uniquement.
  • \n
  • memory_order_consume — rarement utilisé, dépend des données.
  • \n
  • memory_order_acquire — toutes les lectures/écritures suivantes ne peuvent pas être réordonnées avant cette opération.
  • \n
  • memory_order_release — toutes les écritures précédentes deviennent visibles pour les autres threads après cette opération.
  • \n
  • memory_order_acq_rel — combinaison d'acquire et de release.
  • \n
  • memory_order_seq_cst — le plus strict, cohérence séquentielle globale (par défaut).
  • \n
\n\n

Exemple d'utilisation :

\n
#include <atomic>\n#include <thread>\n#include <cassert>\n\nstd::atomic<bool> ready{false};\nint data = 0;\n\nvoid producer() {\n    data = 42;                                   // écriture des données\n    ready.store(true, std::memory_order_release); // publication du drapeau\n}\n\nvoid consumer() {\n    while (!ready.load(std::memory_order_acquire)) // attente du drapeau\n        ;\n    assert(data == 42); // garantie de voir data == 42\n}\n\nint main() {\n    std::thread t1(producer);\n    std::thread t2(consumer);\n    t1.join();\n    t2.join();\n    return 0;\n}
\n\n

Ici, release et acquire garantissent que l'écriture de data a lieu avant la définition du drapeau, et la lecture après. C'est plus rapide qu'un mutex.

\n\n

2. Structures de données lock-free

\n\n

Les algorithmes lock-free permettent d'éviter les verrouillages en utilisant des opérations atomiques et le CAS (Compare-And-Swap). Un exemple classique est la pile lock-free basée sur une liste chaînée simple.

\n\n

Implémentation d'une pile lock-free :

\n
#include <atomic>\n#include <memory>\n\ntemplate<typename T>\nclass LockFreeStack {\nprivate:\n    struct Node {\n        T data;\n        Node* next;\n        Node(const T& val) : data(val), next(nullptr) {}\n    };\n    std::atomic<Node*> head{nullptr};\n\npublic:\n    void push(const T& val) {\n        Node* new_node = new Node(val);\n        new_node->next = head.load(std::memory_order_relaxed);\n        while (!head.compare_exchange_weak(\n            new_node->next, new_node,\n            std::memory_order_release,\n            std::memory_order_relaxed)) {\n            // boucle jusqu'à ce que le CAS réussisse\n        }\n    }\n\n    bool pop(T& result) {\n        Node* old_head = head.load(std::memory_order_relaxed);\n        while (old_head != nullptr &&\n               !head.compare_exchange_weak(\n                   old_head, old_head->next,\n                   std::memory_order_acquire,\n                   std::memory_order_relaxed)) {\n            // attente\n        }\n        if (old_head == nullptr) return false;\n        result = old_head->data;\n        // Attention : dans du code réel, un garbage collector ou des hazard pointers sont nécessaires\n        delete old_head;\n        return true;\n    }\n};
\n\n

Remarques importantes :

\n
    \n

Blogs

Book Recommendations