Introduction aux files d'attente de tâches en Rust
\n\nLes files d'attente de tâches sont l'un des modèles fondamentaux de la programmation concurrente. Elles permettent d'organiser efficacement le transfert de travail entre les threads, simplifiant ainsi l'architecture des applications. En Rust, avec son système strict de possession et de types, travailler avec des files d'attente nécessite une approche particulière, mais offre en retour une sécurité et des performances incroyables.
\n\nDans cet article, nous allons voir comment créer et utiliser des files d'attente de tâches en Rust à partir de zéro. Vous découvrirez les outils standards du langage, tels que les canaux mpsc (multi-producteur, mono-consommateur), et vous familiariserez avec les bibliothèques populaires pour le traitement asynchrone. Ce contenu est destiné aux développeurs débutants déjà familiers avec les bases de la syntaxe Rust.
Les bases : qu'est-ce qu'une file d'attente de tâches et pourquoi en a-t-on besoin
\n\nUne file d'attente de tâches est une structure de données fonctionnant selon le principe FIFO (First In, First Out). Dans le contexte du multithreading, elle permet à certains threads (producteurs) d'ajouter des tâches, et à d'autres (consommateurs) de les récupérer et de les exécuter.
\n\nLes principaux avantages de l'utilisation des files d'attente :
\n\n- \n
- Séparation des responsabilités — la logique de génération des tâches est séparée de leur exécution. \n
- Équilibrage de charge — on peut lancer plusieurs threads consommateurs pour un traitement parallèle. \n
- Gestion du flux de données — la file d'attente lisse les pics de charge, évitant la surcharge du système. \n
- Simplicité des tests — chaque composant peut être testé isolément. \n
En Rust, la bibliothèque standard fournit le module std::sync::mpsc pour créer des canaux avec un seul récepteur. C'est un outil idéal pour commencer.
Création d'une file d'attente simple avec mpsc
\n\nCommençons par un exemple de base : un producteur envoie des nombres, un consommateur les traite.
\n\nuse std::sync::mpsc;\nuse std::thread;\nuse std::time::Duration;\n\nfn main() {\n // Création du canal : tx — émetteur, rx — récepteur\n let (tx, rx) = mpsc::channel();\n\n // Lancement du thread producteur\n thread::spawn(move || {\n for i in 1..=5 {\n println!("Envoi : {}", i);\n tx.send(i).unwrap();\n thread::sleep(Duration::from_millis(500));\n }\n });\n\n // Thread consommateur (thread principal)\n for received in rx {\n println!("Reçu : {}", received);\n }\n}\n\n\nDans cet exemple, tx (transmetteur) peut être cloné pour plusieurs producteurs, tandis que rx (récepteur) est le seul consommateur. La méthode send retourne un Result que nous traitons avec unwrap() — dans un code réel, il faut gérer les erreurs plus correctement.
Producteurs multiples
\n\nPour créer plusieurs threads producteurs, on clone l'émetteur :
\n\nuse std::sync::mpsc;\nuse std::thread;\n\nfn main() {\n let (tx, rx) = mpsc::channel();\n let tx1 = tx.clone();\n\n thread::spawn(move || {\n for i in 1..=3 {\n tx1.send(format!("Thread 1 : message {}", i)).unwrap();\n }\n });\n\n thread::spawn(move || {\n for i in 1..=3 {\n tx.send(format!("Thread 2 : message {}", i)).unwrap();\n }\n });\n\n for received in rx {\n println!("{}", received);\n }\n}\n\n\nNotez que le tx original est déplacé dans le second thread, donc nous le clonons avant le déplacement.
Techniques avancées : types de tâches et gestion des erreurs
\n\nDans les applications réelles, les tâches sont rarement de simples nombres. Ce sont souvent des structures contenant des données et de la logique. Voyons comment organiser une file d'attente avec des tâches de différents types.
\n\nÉnumération pour différents types de tâches
\n\nuse std::sync::mpsc;\nuse std::thread;\n\n#[derive(Debug)]\nenum Task {\n Compute { a :