C++ Concurrent Queue : Implémentation et Bonnes Pratiques

C++ Concurrent Queue : Implémentation et Bonnes Pratiques

Amine Abidi - Lead Software Engineer C++/Qt - Co-fondateur PointerLab

Publié par Amine Abidi - Lead Software Engineer C++/Qt - Co-fondateur PointerLab

Les files d'attente concurrentes sont essentielles pour gérer la communication entre threads dans les applications multi-threadées. En C++, bien que la STL ne fournisse pas directement de telles structures, il est possible d'en implémenter ou d'utiliser des bibliothèques tierces. Cet article explore les différentes approches pour créer une file d'attente thread-safe en C++, en mettant l'accent sur les meilleures pratiques et les outils disponibles.

Pourquoi une file d'attente concurrente en C++ ?

Dans les applications multi-threadées, il est courant d'avoir des producteurs et des consommateurs qui doivent partager des données de manière sûre. Une file d'attente concurrente permet de :

  • Éviter les conditions de course : en assurant que les opérations d'enfilement et de défilement sont atomiques.
  • Faciliter la communication entre threads : en fournissant une structure partagée pour échanger des données.
  • Améliorer les performances : en réduisant le besoin de verrous explicites et en permettant une meilleure utilisation des ressources.

Implémentation d'une file d'attente thread-safe

Une approche simple pour implémenter une file d'attente thread-safe en C++11 utilise std::mutex et std::condition_variable :

#include <queue>
#include <mutex>
#include <condition_variable>
 
template <typename T>
class ThreadSafeQueue {
public:
    void enqueue(T value) {
        std::lock_guard<std::mutex> lock(mutex_);
        queue_.push(std::move(value));
        cond_var_.notify_one();
    }
 
    T dequeue() {
        std::unique_lock<std::mutex> lock(mutex_);
        cond_var_.wait(lock, [this]{ return !queue_.empty(); });
        T value = std::move(queue_.front());
        queue_.pop();
        return value;
    }
 
private:
    std::queue<T> queue_;
    std::mutex mutex_;
    std::condition_variable cond_var_;
};

Cette implémentation garantit que plusieurs threads peuvent enfileter et défiler des éléments sans interférer les uns avec les autres.

Bibliothèques tierces pour les files d'attente concurrentes

Plusieurs bibliothèques offrent des implémentations optimisées de files d'attente concurrentes :

  • Intel TBB (tbb::concurrent_queue) : une file d'attente thread-safe qui permet des opérations de type FIFO sans nécessiter de verrous explicites.
  • Moodycamel's ConcurrentQueue : une file d'attente lock-free hautes performances adaptée aux environnements à haute concurrence.
  • Boost.Lockfree : fournit des structures de données lock-free, y compris des files d'attente, pour des performances optimales dans les systèmes multi-threadés.

Ces bibliothèques sont recommandées pour les applications nécessitant des performances élevées et une gestion efficace des ressources.

Cas d'usage classique

Un exemple typique d'utilisation d'une file d'attente concurrente est le modèle producteur-consommateur :

#include <thread>
#include <iostream>
 
ThreadSafeQueue<int> queue;
 
void producer() {
    for (int i = 0; i < 10; ++i) {
        queue.enqueue(i);
        std::cout << "Produit : " << i << std::endl;
    }
}
 
void consumer() {
    for (int i = 0; i < 10; ++i) {
        int value = queue.dequeue();
        std::cout << "Consommé : " << value << std::endl;
    }
}
 
int main() {
    std::thread t1(producer);
    std::thread t2(consumer);
    t1.join();
    t2.join();
    return 0;
}

Dans cet exemple, le producteur enfile des entiers dans la file, tandis que le consommateur les défie. La synchronisation est gérée par la ThreadSafeQueue.

Quiz : Testez vos connaissances

  1. Quelle est la principale différence entre une file d'attente verrouillée (mutex) et une file d'attente lock-free ?

    • A. Les files d'attente verrouillées sont plus rapides.
    • B. Les files d'attente lock-free évitent les blocages en utilisant des opérations atomiques.
    • C. Les files d'attente verrouillées ne nécessitent pas de synchronisation.
  2. Quel est l'avantage principal d'utiliser std::condition_variable dans une file d'attente thread-safe ?

    • A. Elle permet de dormir le thread jusqu'à ce qu'une condition soit remplie.
    • B. Elle remplace le besoin de std::mutex.
    • C. Elle rend la file d'attente lock-free.

Réponses au quiz

  1. B : Les files d'attente lock-free utilisent des opérations atomiques pour éviter les blocages, ce qui peut améliorer les performances dans certains cas.

  2. A : std::condition_variable permet de suspendre un thread jusqu'à ce qu'une certaine condition soit vraie, évitant ainsi le polling actif et économisant les ressources.


Découvrez l'ensemble de notre blog sur le C++

Rejoignez la communauté C++ 🇫🇷 sur Discord !

Un espace convivial pour échanger et apprendre ensemble.