Introduction : pourquoi les files d'attente de tâches en C++ ?
\n\nDans la programmation moderne, en particulier lors du travail avec des applications multithreadées, il est souvent nécessaire d'organiser l'exécution des tâches dans l'ordre de leur arrivée. Les files d'attente de tâches (task queues) sont un modèle fondamental qui permet de répartir efficacement le travail entre les threads, de traiter les requêtes des serveurs web, de gérer les processus en arrière-plan et d'éviter les conditions de concurrence.
\n\nEn C++, la bibliothèque standard fournit le conteneur puissant std::queue, qui est parfaitement adapté à l'implémentation de files d'attente de tâches. Cependant, en pratique, il ne suffit pas de stocker des éléments ; il faut les transférer en toute sécurité entre les threads, gérer les erreurs et administrer le cycle de vie des tâches. Dans cet article, nous allons voir comment créer des files d'attente de tâches en C++ à partir de zéro, en utilisant les fonctionnalités modernes du langage (C++11 et versions ultérieures).
Nous aborderons :
\n- \n
- Le fonctionnement de base avec
std::queue\n - Une file d'attente de tâches thread-safe \n
- L'implémentation d'un pool de threads (thread pool) \n
- Les files d'attente prioritaires \n
1. File d'attente de tâches de base avec std::queue
\n\nCommençons par un exemple simple. std::queue est un adaptateur de conteneur qui fonctionne selon le principe FIFO (First In, First Out). Il fournit les méthodes push(), pop(), front() et empty().
Voici comment organiser une file d'attente de tâches où chaque tâche est une fonction (ou un objet appelable) :
\n\n#include <iostream>\n#include <queue>\n#include <functional>\n\nint main() {\n // File d'attente de tâches, où chaque tâche est un std::function<void()>\n std::queue<std::function<void()>> taskQueue;\n\n // Ajout de tâches\n taskQueue.push([]() { std::cout << "Tâche 1 exécutée\\n"; });\n taskQueue.push([]() { std::cout << "Tâche 2 exécutée\\n"; });\n taskQueue.push([]() { std::cout << "Tâche 3 exécutée\\n"; });\n\n // Traitement de la file d'attente\n while (!taskQueue.empty()) {\n auto task = taskQueue.front();\n task(); // Exécution de la tâche\n taskQueue.pop(); // Suppression de la tâche exécutée\n }\n\n return 0;\n}\n\nCe code fonctionne, mais uniquement en mode monothread. Si plusieurs threads ajoutent ou retirent des tâches simultanément, une condition de concurrence se produit. Pour le multithreading, une synchronisation est nécessaire.
\n\n2. File d'attente de tâches thread-safe
\n\nPour rendre la file d'attente sûre pour un accès multithread, nous utilisons un mutex (std::mutex) et une variable conditionnelle (std::condition_variable). C'est le modèle classique producteur-consommateur (producer-consumer).
Implémentation d'une file d'attente thread-safe :
\n\n#include <queue>\n#include <mutex>\n#include <condition_variable>\n#include <functional>\n#include <thread>\n\nclass ThreadSafeQueue {\nprivate:\n std::queue<std::function<void()>> queue_;\n std::mutex mutex_;\n std::condition_variable cond_;\n bool stop_ = false;\n\npublic:\n void push(std::function<void()> task) {\n std::lock_guard<std::mutex> lock(mutex_);\n queue_.push(task);\n cond_.notify_one(); // Notification d'un thread en attente\n }\n\n std::function<void()> pop() {\n std::unique_lock<std::mutex> lock(mutex_);\n cond_.wait(lock, [this]() { return !queue_.empty() || stop_; });\n if (stop_ && queue_.empty()) {\n return nullptr; // Signal d'arrêt\n }\n auto task = queue_.front();\n queue_.pop();\n return task;\n }\n\n void stop() {\n std::lock_guard<std::mutex> lock(mutex_);\n stop_ = true;\n cond_.notify_all(); // Réveil de tous les threads pour terminer\n }\n};\n\nPoints clés :
\n- \n
std::lock_gu