Files d'attente de tâches pour C++ : de la théorie à la pratique
La file d'attente de tâches (task queue) est l'un des modèles clés de la programmation multithread en C++. Elle permet de répartir efficacement le travail entre les threads, en évitant les courses de données et la consommation excessive de ressources. Dans cet article, nous verrons comment créer et utiliser des files d'attente de tâches en C++ en mettant l'accent sur les performances et la sécurité.
Pourquoi une file d'attente de tâches ?
Dans les applications modernes, il est souvent nécessaire d'exécuter de nombreuses petites tâches en parallèle. La création directe de threads pour chaque tâche entraîne des frais généraux : la création d'un thread prend du temps et de la mémoire. La file d'attente de tâches résout ce problème grâce à un pool de threads (thread pool) :
- Réutilisation des threads — les threads ne sont pas détruits après l'exécution d'une tâche, mais retournés dans le pool.
- Équilibrage de charge — les tâches sont réparties uniformément.
- Gestion des ressources — limitation du nombre de threads fonctionnant simultanément.
Implémentation de base d'une file d'attente de tâches en C++
Examinons une file d'attente de tâches simple mais thread-safe utilisant std::queue et des mutex. Pour cela, nous utilisons la bibliothèque standard C++11 et ultérieure.
#include <queue>#include <mutex>#include <condition_variable>#include <functional>#include <thread>#include <vector>
class TaskQueue {public: using Task = std::function<void()>;
explicit TaskQueue(size_t numThreads) : stop(false) { for (size_t i = 0; i < numThreads; ++i) { workers.emplace_back([this] { workerLoop(); }); } }
~TaskQueue() { { std::unique_lock<std::mutex> lock(mutex); stop = true; } condition.notify_all(); for (auto& worker : workers) { if (worker.joinable()) { worker.join(); } } }
void enqueue(Task task) { { std::unique_lock<std::mutex> lock(mutex); tasks.push(std::move(task)); } condition.notify_one(); }
private: void workerLoop() { while (true) { Task task; { std::unique_lock<std::mutex> lock(mutex); condition.wait(lock, [this] { return stop || !tasks.empty(); }); if (stop && tasks.empty()) { return; } task = std::move(tasks.front()); tasks.pop(); } task(); // Exécution de la tâche } }
std::queue<Task> tasks; std::mutex mutex; std::condition_variable condition; bool stop; std::vector<std::thread> workers;};Exemple d'utilisation
Montrons maintenant comment utiliser notre classe TaskQueue pour exécuter en parallèle des calculs simples :
#include <iostream>
int main() { TaskQueue tq(4); // Pool de 4 threads
for (int i = 0; i < 10; ++i) { tq.enqueue([i] { std::cout << "Tâche " << i << " exécutée par le thread " << std::this_thread::get_id() << std::endl; }); }
// Le destructeur de tq attendra la fin de toutes les tâches return 0;}Fonctionnalités avancées : file d'attente avec priorités
Il est parfois nécessaire de traiter les tâches par ordre d'importance. Pour cela, on peut utiliser std::priority_queue au lieu d'une file d'attente standard. Ajoutons la prise en charge des priorités :
#include <queue>#include <functional>
class PriorityTaskQueue {public: using Task = std::function<void()>;
struct PrioritizedTask { int priority; Task t Aussi dans la bibliothèque
gRPC pour Java : bibliothèques, configuration et exemples d'utilisation
SQLDelight Kotlin : guide complet avec exemples concrets
Génération de rapports PDF en Go : aperçu des bibliothèques et exemples de code
Jetons JWT en PHP : Guide complet d'authentification et d'autorisation