0Pricing
Learn Rust Coding · Leçon

Concurrence sans crainte avec les threads

Plongez dans les primitives de Rust pour la programmation concurrente, notamment les threads et le passage de messages, tout en garantissant l’absence de courses aux données.

Concurrence sans crainte avec les threads est une leçon Learn Rust Coding gratuite sur CoddyKit. Ceci est la leçon 3 sur 3. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage Learn Rust Coding, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours Learn Rust Coding comprend 3 leçons au total.

Introduction à la concurrence

Bienvenue dans la concurrence sans crainte avec les fils ! Dans cette leçon, vous apprendrez à faire exécuter plusieurs tâches à vos programmes Rust en même temps.

La concurrence désigne l’exécution de plusieurs calculs apparemment simultanés. Elle est essentielle pour les applications réactives et pour exploiter les processeurs modernes à plusieurs cœurs.

  • Les fils sont des unités d’exécution légères au sein d’un programme.
  • Chaque fil peut exécuter une partie distincte de votre code.
  • Le système de propriété de Rust contribue à prévenir les erreurs courantes liées à la concurrence.

Créer votre premier fil

Rust fournit std::thread::spawn pour créer de nouveaux fils. Vous lui transmettez une fermeture (une fonction anonyme) qui contient le code que le nouveau fil doit exécuter.

Essayez d’exécuter cet exemple pour voir les fils en action :

use std::thread;
use std::time::Duration;

fn main() {
  println!("Hello from the main thread!");

  thread::spawn(|| {
    for i in 1..=5 {
      println!("Hi number {} from the spawned thread!", i);
      thread::sleep(Duration::from_millis(1));
    }
  });

  for i in 1..=3 {
    println!("Hi number {} from the main thread!", i);
    thread::sleep(Duration::from_millis(1));
  }
}

Attendre les fils avec `join`

Dans l’exemple précédent, le fil créé peut ne pas avoir terminé avant que le fil principal se termine. En effet, le fil principal n’attend pas les fils créés par défaut.

Pour garantir qu’un fil créé termine son travail, nous utilisons la méthode join() sur son JoinHandle. Cela bloque le fil actuel jusqu’à ce que le fil rejoint ait terminé.

use std::thread;
use std::time::Duration;

fn main() {
  let handle = thread::spawn(|| {
    for i in 1..=5 {
      println!("Thread: {}", i);
      thread::sleep(Duration::from_millis(1));
    }
  });

  for i in 1..=3 {
    println!("Main: {}", i);
    thread::sleep(Duration::from_millis(1));
  }

  handle.join().unwrap(); // Wait for the spawned thread to finish
  println!("Spawned thread has finished!");
}

Déplacer des données dans des fils

Lorsque vous utilisez des variables de l’environnement dans une fermeture spawn, les règles de propriété de Rust entrent en jeu. Par défaut, les fermetures essaient d’emprunter les variables.

Si le thread créé survit à la portée du thread principal dans laquelle la variable a été définie, cela peut entraîner une référence pendante. Pour corriger ce problème, utilisez le mot-clé move avant les paramètres de la fermeture.

  • move force la fermeture à prendre possession des variables qu’elle capture.
  • Cela garantit que les données restent valides pendant toute la durée de vie du nouveau thread.
use std::thread;

fn main() {
  let data = String::from("Hello from outer scope");

  let handle = thread::spawn(move || { // Use 'move' to take ownership of 'data'
    println!("Data in thread: {}", data);
  });

  handle.join().unwrap();
  // println!("Data after thread: {}", data); // This would cause a compile error!
  println!("Main thread finished.");
}

Communiquer par passage de messages

Bien qu’il soit possible de partager directement des données entre threads (état partagé) en Rust, cela nécessite une synchronisation rigoureuse (par exemple avec Mutex et Arc, abordés dans d’autres leçons).

Pour la concurrence, une approche plus sûre et souvent plus simple est le passage de messages. Les threads communiquent en s’envoyant des messages, ce qui évite l’accès direct à une mémoire partagée.

  • Un thread envoie les données.
  • Un autre thread reçoit les données.
  • La bibliothèque standard de Rust fournit std::sync::mpsc à cet effet.

Configurer un canal `mpsc`

std::sync::mpsc signifie Multiple Producer, Single Consumer. Cela signifie que plusieurs threads peuvent envoyer des messages, mais qu’un seul thread peut les recevoir.

Pour créer un canal, vous appelez mpsc::channel(). Il renvoie un tuple contenant :

  • Un Sender (tx) : utilisé pour envoyer des messages.
  • Un Receiver (rx) : utilisé pour recevoir des messages.

Voyons comment en créer un et envoyer un message simple.

use std::sync::mpsc;
use std::thread;

fn main() {
  // Create a new channel
  let (tx, rx) = mpsc::channel();

  thread::spawn(move || {
    let val = String::from("hi");
    tx.send(val).unwrap(); // Send the message
    // println!("val is {}", val); // Error: val moved to tx.send()
  });

  // Receive the message in the main thread
  let received = rx.recv().unwrap();
  println!("Got: {}", received);
}

Envoyer plusieurs messages

Vous pouvez envoyer plusieurs messages via le même canal. Le Receiver possède des méthodes pour gérer cette situation :

  • rx.recv() : bloque le thread actuel jusqu’à la réception d’un message. Renvoie un Result.
  • rx.try_recv() : n’est pas bloquante. Renvoie immédiatement un Result, contenant soit un message, soit une erreur si aucun message n’est disponible.
  • Vous pouvez également parcourir directement le Receiver pour obtenir les messages jusqu’à la fermeture de l’émetteur.
use std::sync::mpsc;
use std::thread;
use std::time::Duration;

fn main() {
  let (tx, rx) = mpsc::channel();

  thread::spawn(move || {
    let msgs = vec!["hi", "from", "the", "thread"];
    for msg in msgs {
      tx.send(String::from(msg)).unwrap();
      thread::sleep(Duration::from_millis(10));
    }
  });

  // Iterate over the receiver to get all messages
  for received in rx {
    println!("Got: {}", received);
  }
  println!("All messages received!");
}

Plusieurs producteurs, un seul consommateur

Le « MP » de mpsc signifie Multiple Producers. Vous pouvez cloner un Sender afin que plusieurs threads envoient des messages au même Receiver.

Chaque Sender cloné partage l’accès au même canal, ce qui permet l’envoi simultané de messages depuis différents threads.

use std::sync::mpsc;
use std::thread;
use std::time::Duration;

fn main() {
  let (tx, rx) = mpsc::channel();

  let tx1 = tx.clone(); // Clone the sender for another thread
  thread::spawn(move || {
    tx1.send(String::from("hello from tx1")).unwrap();
  });

  thread::spawn(move || {
    tx.send(String::from("hello from tx2")).unwrap();
  });

  // Collect all messages
  let mut received_messages: Vec<String> = vec![];
  for received in rx {
    received_messages.push(received);
    if received_messages.len() == 2 { break; } // Assuming 2 messages for this example
  }
  println!("Received: {:?}", received_messages);
}

La concurrence sans crainte avec Rust

Le système de propriété et le système de types de Rust sont essentiels pour parvenir à une concurrence sans crainte. En imposant des règles lors de la compilation, Rust évite les problèmes courants de concurrence, comme les courses aux données.

  • La propriété empêche plusieurs références mutables vers les mêmes données.
  • Le mot-clé move garantit que les données sont transférées en toute sécurité vers un nouveau thread.
  • Le passage de messages (mpsc) évite totalement la mémoire partagée, ce qui rend la communication sûre de par sa conception.

Ces mécanismes vous permettent d’écrire du code concurrent en toute confiance, sachant que le compilateur détectera de nombreux problèmes potentiels.

Vérification des concepts de concurrence

Lesquelles des affirmations suivantes concernant les primitives de concurrence de Rust sont TRUE ?

Récapitulatif : threads et passage de messages

Excellent travail ! Vous avez appris les bases de la concurrence sans crainte en Rust :

  • Nous créons de nouveaux threads avec std::thread::spawn.
  • Nous utilisons JoinHandle::join() pour attendre la fin d’un thread.
  • Le mot-clé move transfère en toute sécurité la propriété des données vers un thread.
  • std::sync::mpsc fournit un système robuste de passage de messages pour la communication entre threads.
  • Les règles de propriété de Rust empêchent les courses aux données lors de la compilation, ce qui rend la programmation concurrente plus sûre.

Cette leçon fournit une base solide pour créer des applications réactives et efficaces. Continuez à vous entraîner !

Questions Fréquemment Posées

La leçon « Concurrence sans crainte avec les threads » est-elle gratuite ?

Oui — le texte complet de « Concurrence sans crainte avec les threads » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours Learn Rust Coding, passe à CoddyKit PRO. Le cours Learn Rust Coding comprend 3 leçons au total.

Qu'est-ce que j'apprendrai dans « Concurrence sans crainte avec les threads » ?

Plongez dans les primitives de Rust pour la programmation concurrente, notamment les threads et le passage de messages, tout en garantissant l’absence de courses aux données. Tu pratiques Learn Rust Coding avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.

Dois-je avoir de l'expérience pour commencer Learn Rust Coding ?

Aucune expérience préalable n'est requise. Learn Rust Coding sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 3 sur 3.

Combien de temps prend la leçon « Concurrence sans crainte avec les threads » ?

La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.

Peux-tu écrire et exécuter du code dans cette leçon Learn Rust Coding ?

Oui. Chaque leçon Learn Rust Coding inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.

Toutes les leçons de ce cours

  1. Pointeurs intelligents Box, Rc et Arc
  2. Mutabilité intérieure : RefCell, Cell
  3. Concurrence sans crainte avec les threads
← Retour à Learn Rust Coding