Lär dig programmera i Rust · Lektion

mpsc-kanaler

Skicka mellan trådar

Lektion 1 av 413 steg

mpsc-kanaler är en gratis lektion i Lär dig programmera i Rust på CoddyKit. Detta är lektion 1 av 4. Du kan läsa vilka 3 lektioner som helst i den här lärvägen kostnadsfritt i sin helhet – därefter låser CoddyKit PRO upp alla lektioner, plus praktisk övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Den ingår i lärvägen för Lär dig programmera i Rust, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i Lär dig programmera i Rust innehåller totalt 4 lektioner.

Vad är en kanal?

En kanal är ett enkelriktat rör för att skicka värden från en tråd till en annan. Rusts standardbibliotek tillhandahåller std::sync::mpsc, där mpsc betyder multiple producer, single consumer.

  • Hälften Sender skickar in värden.
  • Hälften Receiver hämtar ut värden.

Kanaler låter trådar kommunicera genom att skicka meddelanden i stället för att dela minne direkt, vilket undviker många datakapplöpningar.

Skapa en kanal

Anropa mpsc::channel() för att få en tupel med (Sender, Receiver). Här skickar vi ett värde från en skapad tråd tillbaka till huvudtråden.

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

fn main() {
    let (tx, rx) = mpsc::channel();
    thread::spawn(move || {
        tx.send(42).unwrap();
    });
    let received = rx.recv().unwrap();
    println!("Got: {}", received);
}

send och recv

tx.send(value) returnerar en Result: det misslyckas endast om mottagaren har tagits bort. rx.recv() blockerar tills ett värde anländer och returnerar Err när alla sändare är borta.

  • send flyttar ägarskapet för värdet in i kanalen.
  • recv tar över ägarskapet på andra sidan.
use std::sync::mpsc;
use std::thread;

fn main() {
    let (tx, rx) = mpsc::channel();
    thread::spawn(move || {
        let msg = String::from("hello from thread");
        tx.send(msg).unwrap();
    });
    let text = rx.recv().unwrap();
    println!("{}", text);
}

Ägarskapet flyttas genom kanalen

Eftersom send tar värdet genom värdeöverföring kan du inte använda det efter att det har skickats. Denna kontroll vid kompilering garanterar att ingen tråd behåller en inaktuell referens till data som nu ägs av en annan tråd.

Om du försökte skriva ut msg efter send nedan skulle det bli ett kompileringsfel, så vi använder det bara en gång.

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

fn main() {
    let (tx, rx) = mpsc::channel();
    thread::spawn(move || {
        let data = vec![1, 2, 3];
        tx.send(data).unwrap();
        // data is moved; cannot use it here
    });
    let v = rx.recv().unwrap();
    println!("Sum: {}", v.iter().sum::<i32>());
}

Iterera över en Receiver

En Receiver implementerar IntoIterator. En loop över den ger varje värde tills kanalen stängs (alla sändare har tagits bort). Detta är det idiomatiska sättet att konsumera en ström av meddelanden.

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

fn main() {
    let (tx, rx) = mpsc::channel();
    thread::spawn(move || {
        for i in 1..=3 {
            tx.send(i).unwrap();
        }
    });
    for received in rx {
        println!("Received: {}", received);
    }
}

Flera producenter med clone

mp i mpsc betyder att du kan ha flera sändare. Klona Sender och ge en kopia till varje tråd. Mottagaren samlar in allt tills varje klon har tagits bort.

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

fn main() {
    let (tx, rx) = mpsc::channel();
    let tx2 = tx.clone();
    thread::spawn(move || { tx.send("from A").unwrap(); });
    thread::spawn(move || { tx2.send("from B").unwrap(); });
    for msg in rx {
        println!("{}", msg);
    }
}

Semantik för kanalstängning

Mottagarloopen avslutas automatiskt när alla sändare har tagits bort. Om ens en Sender fortfarande lever blockerar for msg in rx för alltid i väntan på fler meddelanden. Ta alltid bort sändare eller begränsa deras räckvidd korrekt så att loopen kan avslutas.

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

fn main() {
    let (tx, rx) = mpsc::channel();
    let handle = thread::spawn(move || {
        for i in 0..3 {
            tx.send(i * 10).unwrap();
        }
        // tx dropped here, closing the channel
    });
    handle.join().unwrap();
    let total: i32 = rx.iter().sum();
    println!("Total: {}", total);
}

try_recv för icke-blockerande läsning

recv blockerar, men try_recv returnerar omedelbart ett Result. Den ger Ok(value) om ett meddelande är redo, eller Err om kanalen är tom eller frånkopplad. Det är användbart i händelseloopar som måste fortsätta utföra annat arbete.

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

fn main() {
    let (tx, rx) = mpsc::channel();
    thread::spawn(move || {
        thread::sleep(Duration::from_millis(50));
        tx.send("ready").unwrap();
    });
    loop {
        match rx.try_recv() {
            Ok(msg) => { println!("{}", msg); break; }
            Err(_) => println!("waiting..."),
        }
        thread::sleep(Duration::from_millis(20));
    }
}

Skicka anpassade typer

Alla typer som är Send kan färdas genom en kanal, inklusive dina egna structar och enum-typer. Enum-typer passar utmärkt för att modellera olika meddelandetyper i ett arbetarkprotokoll.

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

enum Job {
    Print(String),
    Add(i32, i32),
}

fn main() {
    let (tx, rx) = mpsc::channel();
    thread::spawn(move || {
        tx.send(Job::Print(String::from("hi"))).unwrap();
        tx.send(Job::Add(2, 3)).unwrap();
    });
    for job in rx {
        match job {
            Job::Print(s) => println!("print: {}", s),
            Job::Add(a, b) => println!("add: {}", a + b),
        }
    }
}

sync_channel och backpressure

mpsc::sync_channel(n) skapar en begränsad kanal med buffertstorleken n. När bufferten är full blockerar send tills utrymme blir ledigt. Detta skapar backpressure och hindrar en snabb producent från att överbelasta en långsam konsument.

  • sync_channel(0) är en rendezvous-kanal: send och recv måste mötas.
use std::sync::mpsc;
use std::thread;

fn main() {
    let (tx, rx) = mpsc::sync_channel(2);
    thread::spawn(move || {
        for i in 1..=4 {
            tx.send(i).unwrap();
            println!("sent {}", i);
        }
    });
    for v in rx {
        println!("got {}", v);
    }
}

Ett enkelt arbetarmönster

Kanaler passar utmärkt för mönstret producent/konsument: en tråd producerar arbetsobjekt och en annan konsumerar och bearbetar dem. Här producerar huvudtråden tal och en arbetstråd kvadrerar varje tal och rapporterar tillbaka via en andra kanal.

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

fn main() {
    let (job_tx, job_rx) = mpsc::channel();
    let (res_tx, res_rx) = mpsc::channel();
    thread::spawn(move || {
        for n in job_rx {
            res_tx.send(n * n).unwrap();
        }
    });
    for n in 1..=4 {
        job_tx.send(n).unwrap();
    }
    drop(job_tx);
    for r in res_rx {
        println!("square: {}", r);
    }
}

Snabb kontroll

Testa din förståelse av mpsc-kanaler.

Sammanfattning

Du har lärt dig grunderna i mpsc-kanaler:

  • mpsc::channel() returnerar ett par av typen (Sender, Receiver).
  • send flyttar in ett värde; recv blockerar tills det kan hämta ut ett.
  • Att iterera över en mottagare konsumerar meddelanden tills alla sändare har släppts.
  • Klona Sender för flera producenter.
  • try_recv blockerar inte; sync_channel(n) lägger till begränsad backpressure.

Kanaler låter trådar dela data säkert genom att överföra ägarskap i stället för att dela minne.

Gratis att börja

Lär dig Rust med en AI-lärare – gratis

Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.

Kurser
39
Lektioner
144

Vanliga frågor

Är lektionen ”mpsc-kanaler” gratis?

Ja – du kan läsa vilka 3 lektioner som helst i lärvägen Lär dig programmera i Rust, inklusive ”mpsc-kanaler”, kostnadsfritt i sin helhet här på webben. Därefter låser CoddyKit PRO upp alla lektioner, plus interaktiv övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Kursen i Lär dig programmera i Rust innehåller totalt 4 lektioner.

Vad lär jag mig i ”mpsc-kanaler”?

Skicka mellan trådar Ni övar på Lär dig programmera i Rust med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.

Behöver jag någon erfarenhet för att börja lära mig Lär dig programmera i Rust?

Du behöver inga förkunskaper. Utbildningen i Lär dig programmera i Rust på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 1 av 4.

Hur lång tid tar lektionen ”mpsc-kanaler”?

De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.

Kan jag skriva och köra kod i den här Lär dig programmera i Rust-lektionen?

Ja. Varje Lär dig programmera i Rust-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.

Alla lektioner i den här kursen

  1. mpsc-kanaler
  2. Dela tillstånd med Arc/Mutex
  3. Avgränsade trådar
  4. Crossbeam-kanaler
← Tillbaka till Lär dig programmera i Rust