Gestion des files d'attente de tâches en Rust : du simple au complexe
Les files d'attente de tâches sont un modèle fondamental de la programmation asynchrone, permettant de répartir efficacement les ressources de calcul entre plusieurs opérations. En Rust, un langage de programmation système axé sur la sécurité et la performance, l'implémentation des files d'attente de tâches se révèle particulièrement brillante grâce au système de possession, aux types de données et aux runtimes asynchrones puissants comme tokio et async-std.
Dans cet article, nous allons voir comment créer, gérer et optimiser des files d'attente de tâches en Rust. Vous découvrirez les approches synchrones et asynchrones, apprendrez à utiliser les canaux pour la transmission de messages et vous familiariserez avec des exemples pratiques que vous pourrez appliquer immédiatement dans vos projets.
Pourquoi des files d'attente de tâches en Rust ?
Dans les applications modernes, il est souvent nécessaire d'exécuter des tâches de manière asynchrone : traiter des requêtes HTTP, écrire dans une base de données, effectuer des calculs en arrière-plan. La file d'attente de tâches permet de :
- Séparer la mise en file d'attente de la tâche et son exécution — un thread (ou composant) place la tâche dans la file, un autre la récupère et la traite.
- Contrôler la charge — limiter le nombre de tâches exécutées simultanément, évitant ainsi la surcharge du système.
- Simplifier la mise à l'échelle — ajouter plus de workers (threads de traitement) sans modifier la logique de mise en file d'attente des tâches.
- Améliorer la tolérance aux pannes — en cas de défaillance d'un worker, la tâche reste dans la file et peut être traitée par un autre.
Rust, grâce à son système de types strict et à l'absence de ramasse-miettes, permet de créer des files d'attente hautes performances avec une surcharge minimale.
File d'attente synchrone avec std::sync::mpsc
La bibliothèque standard de Rust fournit le module std::sync::mpsc (Multiple Producer, Single Consumer) — un canal multithreadé pour la transmission de messages. C'est le moyen le plus simple d'organiser une file d'attente de tâches.
Prenons un exemple : créons une file d'attente dans laquelle le thread principal envoie des nombres et le thread de travail les élève au carré.
use std::sync::mpsc;use std::thread;use std::time::Duration;
fn main() { // Création du canal : expéditeur et récepteur let (tx, rx) = mpsc::channel::<i32>();
// Thread worker : traite les tâches de la file let worker = thread::spawn(move || { for received in rx { println!("Reçu : {}, carré : {}", received, received * received); thread::sleep(Duration::from_millis(100)); } });
// Envoi des tâches dans la file for i in 1..=10 { tx.send(i).unwrap(); println!("Envoyé : {}", i); }
// Fermeture du canal (automatique à la chute de tx) drop(tx);
// Attente de la fin du worker worker.join().unwrap();}Ce code illustre le modèle de base : tx envoie les données, rx les reçoit dans un thread séparé. Le canal synchronise automatiquement l'accès — si le worker est occupé, l'expéditeur attendra.
Limitation de mpsc : un seul consommateur. Pour plusieurs workers, il faudra soit combiner les canaux, soit utiliser des bibliothèques plus avancées.
File d'attente asynchrone avec Tokio et tokio::sync::mpsc
Pour les applications asynchrones (serveurs web, chats, traitement d'événements), il est préférable d'utiliser tokio — un runtime asynchrone populaire pour Rust. Son canal tokio::sync::mpsc fonctionne avec async/await et prend en charge plusieurs producteurs et consommateurs.
Ajoutez dans Cargo.toml :
[dependencies]tokio = { version = "1", features = ["full"] }Exemple de file d'attente asynchrone :
use tokio::sync::mpsc;use tokio::time::{sleep, Duration};
#[tokio: