Introduction : pourquoi les files d'attente de tâches en Rust ?
Lors du développement d'applications en Rust, il est souvent nécessaire de traiter des tâches de manière asynchrone ou de répartir la charge entre plusieurs threads. Une file d'attente de tâches (task queue) est une structure de données fondamentale qui permet d'organiser le traitement séquentiel ou concurrent d'unités de travail. Dans cet article, nous allons voir comment créer et utiliser des files d'attente de tâches en Rust, en commençant par des exemples simples et en terminant par des scénarios réels.
Rust propose plusieurs approches pour implémenter des files d'attente : des canaux standards de std::sync::mpsc aux solutions asynchrones avancées basées sur tokio. Nous examinerons chacune d'elles et montrerons comment choisir le bon outil pour votre tâche.
Bases : file d'attente de tâches via les canaux mpsc
La façon la plus simple de créer une file d'attente de tâches en Rust est d'utiliser un canal multi-producteur, mono-consommateur (mpsc) de la bibliothèque standard. Ce mécanisme est idéal pour les situations où un seul thread traite les tâches envoyées par plusieurs threads producteurs.
Création d'une file d'attente simple
Considérons un exemple de base où le thread principal envoie des tâches et un thread séparé les traite :
use std::sync::mpsc;use std::thread;use std::time::Duration;
fn main() { // Création du canal : émetteur et récepteur let (tx, rx) = mpsc::channel::<String>();
// Lancement du thread de traitement let handler = thread::spawn(move || { for task in rx { println!("Traitement de la tâche : {}", task); thread::sleep(Duration::from_millis(500)); } });
// Envoi des tâches let tasks = vec!["Tâche 1", "Tâche 2", "Tâche 3"]; for task in tasks { tx.send(task.to_string()).unwrap(); println!("Envoyé : {}", task); }
// Fermeture du canal pour que le gestionnaire se termine drop(tx); handler.join().unwrap();}Ce code illustre le mécanisme de base : tx.send() place une tâche dans la file d'attente, et le récepteur rx les extrait dans l'ordre d'arrivée (FIFO). Notez que le canal bloque automatiquement le producteur si la file d'attente est pleine, et bloque le consommateur si la file d'attente est vide.
Producteurs multiples
L'un des principaux avantages de mpsc est la possibilité de cloner l'émetteur pour plusieurs threads :
use std::sync::mpsc;use std::thread;
fn main() { let (tx, rx) = mpsc::channel::<u32>(); let mut handles = vec![];
// Lancement de 3 threads producteurs for id in 0..3 { let tx_clone = tx.clone(); handles.push(thread::spawn(move || { for i in 0..5 { let task = id * 100 + i; tx_clone.send(task).unwrap(); } })); }
// Fermeture de l'émetteur original drop(tx);
// Traitement de toutes les tâches for received in rx { println!("Tâche reçue : {}", received); }
for handle in handles { handle.join().unwrap(); }}Chaque producteur envoie ses propres tâches, et un seul consommateur les traite dans l'ordre d'arrivée. C'est le modèle classique "producteur-consommateur".
File d'attente avancée avec priorités
Le canal mpsc standard ne prend pas en charge les priorités — les tâches sont traitées strictement dans l'ordre d'envoi. Cependant, nous pouvons implémenter une file d'attente avec priorités en utilisant BinaryHeap de la bibliothèque standard.
Implémentation d'une file d'attente prioritaire
use std::collections::BinaryHeap;use std::cmp::Ordering;
#[derive(Debug, Eq, PartialEq)]struct Task { priority: u8, data: String,}
impl Ord for Task { fn cmp(&self, other: &Self) -> Ordering { // Plus le nombre est petit, plus la priorité est élevée other.priority.cmp(&self