Les programmes utilitaires qui tournent en arrière-plan doivent gérer la concurrence : surveiller des événements, répondre aux inputs utilisateur, et maintenir un état partagé entre plusieurs tâches. Rust fournit des primitives de concurrence sûres qui permettent d’écrire des programmes multi-thread sans data races. Dans cette unité, vous apprendrez à créer des threads, à les faire communiquer via des channels, et à partager de l’état avec Arc et Mutex.
Créer des threads avec std::thread
La bibliothèque standard de Rust fournit std::thread pour créer des threads système. La closure passée à thread::spawn doit utiliser move pour capturer les variables par valeur :
use std::thread;
use std::time::Duration;
fn main() {
let handle = thread::spawn(move || {
for i in 0..5 {
println!("Thread secondaire : {}", i);
thread::sleep(Duration::from_millis(100));
}
});
for i in 0..3 {
println!("Thread principal : {}", i);
thread::sleep(Duration::from_millis(150));
}
handle.join().unwrap(); // attendre que le thread termine
println!("Thread secondaire terminé");
}
Communication avec les channels (mpsc)
Les channels permettent à des threads de s’envoyer des messages de façon sûre. Rust fournit des channels multi-producer, single-consumer (mpsc) : plusieurs émetteurs, un seul récepteur.
use std::sync::mpsc;
use std::thread;
#[derive(Debug)]
enum MessageSurveillance {
ReferenceDetectee(String),
Arret,
}
fn main() {
let (tx, rx) = mpsc::channel();
let tx_clone = tx.clone();
thread::spawn(move || {
let refs = vec!["DOC-2024-001", "FACT-2023-042"];
for r in refs {
tx_clone.send(MessageSurveillance::ReferenceDetectee(r.to_string())).unwrap();
thread::sleep(std::time::Duration::from_secs(1));
}
tx_clone.send(MessageSurveillance::Arret).unwrap();
});
loop {
match rx.recv().unwrap() {
MessageSurveillance::ReferenceDetectee(r) => println!("Détectée : {}", r),
MessageSurveillance::Arret => { println!("Arrêt"); break; }
}
}
}
Partager l’état avec Arc<Mutex<T>>
Quand plusieurs threads doivent accéder au même état, on utilise Arc (Atomic Reference Counting, le pointeur partagé thread-safe) combiné avec Mutex (pour l’accès exclusif).
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let references = Arc::new(Mutex::new(Vec::<String>::new()));
let mut handles = vec![];
for i in 0..3 {
let refs_clone = Arc::clone(&references);
let handle = thread::spawn(move || {
let ref_simulee = format!("DOC-2024-{:03}", i + 1);
let mut guard = refs_clone.lock().unwrap();
guard.push(ref_simulee);
});
handles.push(handle);
}
for h in handles { h.join().unwrap(); }
println!("{:?}", references.lock().unwrap());
}
Concevoir une boucle événementielle
Pour un utilitaire en arrière-plan, on structure généralement le programme autour d’une boucle événementielle qui traite les messages reçus via un channel :
use std::sync::mpsc;
use std::time::Duration;
use std::thread;
enum Evenement {
ClipboardChange(String),
RaccourciClavier(String),
Quitter,
}
fn demarrer_surveillance(tx: mpsc::Sender<Evenement>) {
thread::spawn(move || {
loop {
thread::sleep(Duration::from_millis(500));
// Ici : lire le clipboard avec la crate arboard
// tx.send(Evenement::ClipboardChange(texte)).ok();
}
});
}
fn boucle_principale(rx: mpsc::Receiver<Evenement>) {
loop {
match rx.recv_timeout(Duration::from_secs(5)) {
Ok(Evenement::ClipboardChange(t)) => println!("Clipboard: {}", t),
Ok(Evenement::Quitter) => break,
Ok(_) | Err(_) => {},
}
}
}
thread::spawn(move || ...)pour créer un thread qui capture son environnementmpsc::channel()pour la communication inter-threads :tx.send()/rx.recv()Arc<Mutex<T>>pour partager de l’état mutable entre threads en toute sécurité- Structurez les programmes en arrière-plan autour d’une boucle événementielle et de channels
📚 Formation : Rust : Développer des utilitaires système

-
Add a note