0Pricing
Learn Rust Coding · Leçon

Threads à portée limitée

Emprunter entre threads

Threads à portée limitée est une leçon Learn Rust Coding gratuite sur CoddyKit. Ceci est la leçon 3 sur 4. 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 4 leçons au total.

Le problème des emprunts dans les threads

Une fermeture normale de thread::spawn doit être 'static : elle ne peut pas emprunter de variables locales, car le thread pourrait vivre plus longtemps que la fonction qui les possède. C’est pourquoi vous voyez si souvent move et Arc.

Les threads à portée résolvent ce problème en garantissant que chaque thread se termine avant la fin de la portée, ce qui rend sûr l’emprunt de données locales.

Pourquoi spawn nécessite 'static

Avec thread::spawn, le thread créé peut continuer à s’exécuter après le retour de main ou de toute autre fonction. S’il avait emprunté une variable locale, celle-ci pourrait être détruite alors que le thread l’utilise encore. Rust l’interdit lors de la compilation et vous oblige à déplacer les données possédées dans la fermeture.

use std::thread;

fn main() {
    let nums = vec![1, 2, 3];
    // move transfers ownership into the thread
    let handle = thread::spawn(move || {
        println!("in thread: {:?}", nums);
    });
    handle.join().unwrap();
}

Présentation de thread::scope

Stabilisé dans Rust 1.63, std::thread::scope crée une portée dans laquelle les threads peuvent emprunter des variables locales. La portée bloque jusqu’à ce que tous les threads qu’elle contient soient terminés, de sorte que les emprunts ne peuvent jamais être invalides.

Vous créez un thread avec s.spawn(...), en utilisant le gestionnaire de portée s à la place de thread::spawn.

use std::thread;

fn main() {
    let data = vec![10, 20, 30];
    thread::scope(|s| {
        s.spawn(|| {
            println!("borrowed: {:?}", data);
        });
    });
    // data is still usable here
    println!("after scope: {:?}", data);
}

Emprunter sans move

Dans thread::scope, vous pouvez lire des variables locales par référence sans move. Plusieurs threads à portée peuvent partager simultanément un emprunt immuable des mêmes données, comme avec des références normales.

use std::thread;

fn main() {
    let message = String::from("shared text");
    thread::scope(|s| {
        s.spawn(|| println!("thread 1 sees: {}", message));
        s.spawn(|| println!("thread 2 sees: {}", message));
    });
    println!("main still owns: {}", message);
}

Répartir le travail sur une tranche

Un modèle courant consiste à diviser une tranche et à laisser chaque thread traiter un fragment. Les threads à portée rendent cette approche simple, car chaque thread peut emprunter directement une partie de la tranche d’origine, sans clonage.

use std::thread;

fn main() {
    let numbers = [1, 2, 3, 4, 5, 6];
    let (left, right) = numbers.split_at(3);
    thread::scope(|s| {
        s.spawn(|| {
            let sum: i32 = left.iter().sum();
            println!("left sum:  {}", sum);
        });
        s.spawn(|| {
            let sum: i32 = right.iter().sum();
            println!("right sum: {}", sum);
        });
    });
}

Récupérer les valeurs de retour

Comme les threads ordinaires, s.spawn renvoie un ScopedJoinHandle. Appelez .join() pour obtenir la valeur de retour du thread. Vous pouvez récupérer les gestionnaires et les joindre après leur création afin de rassembler les résultats.

use std::thread;

fn main() {
    let inputs = [2, 4, 6];
    let mut handles = vec![];
    thread::scope(|s| {
        for &x in &inputs {
            handles.push(s.spawn(move || x * x));
        }
        let results: Vec<i32> = handles.into_iter()
            .map(|h| h.join().unwrap())
            .collect();
        println!("{:?}", results);
    });
}

Les emprunts mutables demandent de l’attention

Deux threads à portée ne peuvent pas détenir simultanément des emprunts mutables des mêmes données : cela enfreindrait les règles d’aliasage de Rust. Pour modifier des données partagées depuis plusieurs threads, vous avez toujours besoin d’un Mutex, mais un seul thread peut obtenir un emprunt mutable unique de parties disjointes.

Ci-dessous, chaque thread modifie une moitié différente du tableau grâce à split_at_mut.

use std::thread;

fn main() {
    let mut data = [1, 2, 3, 4];
    let (a, b) = data.split_at_mut(2);
    thread::scope(|s| {
        s.spawn(|| { for x in a.iter_mut() { *x *= 10; } });
        s.spawn(|| { for x in b.iter_mut() { *x += 100; } });
    });
    println!("{:?}", data);
}

La portée effectue automatiquement les jonctions

Vous n’avez pas besoin d’appeler join sur chaque thread à portée. Lorsque la fermeture de la scope renvoie son résultat, Rust effectue automatiquement la jonction de tous les threads qui ne l’ont pas encore été avant de poursuivre. C’est pourquoi les emprunts restent valides pendant toute la durée de vie du thread.

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

fn main() {
    let label = String::from("task");
    thread::scope(|s| {
        s.spawn(|| {
            thread::sleep(Duration::from_millis(30));
            println!("{} done", label);
        });
        println!("spawned, scope will wait");
    });
    println!("all scoped threads finished");
}

Combiner une portée avec une mutation partagée

Lorsque les threads doivent modifier la même valeur, combinez des threads à portée avec un Mutex. Vous pouvez vous passer de Arc, car la portée permet déjà aux threads d’emprunter directement le Mutex local.

use std::sync::Mutex;
use std::thread;

fn main() {
    let counter = Mutex::new(0);
    thread::scope(|s| {
        for _ in 0..5 {
            s.spawn(|| {
                let mut n = counter.lock().unwrap();
                *n += 1;
            });
        }
    });
    println!("counter = {}", *counter.lock().unwrap());
}

Threads à portée ou créés&nbsp;: lequel utiliser&nbsp;?

Utilisez des threads à portée lorsque le travail est limité et se termine dans une fonction, et que vous souhaitez emprunter des données de la pile sans Arc ni clonage.

Utilisez thread::spawn lorsqu’un thread doit vivre plus longtemps que la fonction actuelle ou s’exécuter pendant toute la durée de vie du programme. Les threads à portée ne peuvent pas sortir de leur portée.

Exemple de map parallèle

Pour réunir ces notions : voici une petite map parallèle qui transforme chaque élément d’un vecteur dans son propre thread tout en empruntant les entrées, puis rassemble les résultats dans l’ordre.

use std::thread;

fn parallel_double(items: &[i32]) -> Vec<i32> {
    let mut handles = Vec::new();
    let mut out = Vec::new();
    thread::scope(|s| {
        for &x in items {
            handles.push(s.spawn(move || x * 2));
        }
        for h in handles {
            out.push(h.join().unwrap());
        }
    });
    out
}

fn main() {
    let nums = vec![1, 2, 3, 4];
    println!("{:?}", parallel_double(&nums));
}

Vérification rapide

Vérifiez votre compréhension des threads à portée.

Récapitulatif

Vous avez découvert les threads à portée :

  • thread::spawn exige des fermetures 'static ; les threads à portée ne l’exigent pas.
  • thread::scope permet aux threads d’emprunter des variables locales en toute sécurité.
  • La portée effectue automatiquement la jonction de tous les threads avant de renvoyer son résultat.
  • Le partage mutable nécessite toujours un Mutex, mais pas de Arc à l’intérieur d’une portée.
  • Utilisez les threads à portée pour un parallélisme limité à une fonction.

Questions Fréquemment Posées

La leçon « Threads à portée limitée » est-elle gratuite ?

Oui — le texte complet de « Threads à portée limitée » 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 4 leçons au total.

Qu'est-ce que j'apprendrai dans « Threads à portée limitée » ?

Emprunter entre threads 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 4.

Combien de temps prend la leçon « Threads à portée limitée » ?

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. Canaux mpsc
  2. Partager l’état avec Arc/Mutex
  3. Threads à portée limitée
  4. Canaux Crossbeam
← Retour à Learn Rust Coding