Learn Rust Coding · Lekcja

Kanały mpsc

Wysyłanie między wątkami

Lekcja 1 z 413 kroki

Kanały mpsc to bezpłatna lekcja Learn Rust Coding na CoddyKit. To lekcja 1 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Learn Rust Coding, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Learn Rust Coding zawiera 4 lekcji w sumie.

Czym jest kanał?

Kanał to jednokierunkowa rura służąca do przesyłania wartości z jednego wątku do drugiego. Biblioteka standardowa Rust udostępnia std::sync::mpsc, gdzie mpsc oznacza wielu producentów, jednego konsumenta.

  • Połówka Sender umieszcza wartości w kanale.
  • Połówka Receiver pobiera wartości.

Kanały pozwalają wątkom komunikować się przez przekazywanie komunikatów zamiast bezpośredniego współdzielenia pamięci, co zapobiega wielu wyścigom danych.

Tworzenie kanału

Wywołaj mpsc::channel(), aby otrzymać krotkę (Sender, Receiver). W tym przykładzie wysyłamy jedną wartość z uruchomionego wątku do wątku głównego.

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 i recv

tx.send(value) zwraca Result: kończy się błędem tylko wtedy, gdy odbiornik został usunięty. rx.recv() blokuje działanie do momentu nadejścia wartości, zwracając Err, gdy nie ma już żadnego nadajnika.

  • send przenosi własność wartości do kanału.
  • recv odbiera własność po drugiej stronie.
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);
}

Własność przechodzi przez kanał

Ponieważ send przyjmuje wartość przez wartość, nie możesz użyć jej po wysłaniu. Ta reguła sprawdzana podczas kompilacji gwarantuje, że żaden wątek nie zachowa nieaktualnego odwołania do danych należących już do innego wątku.

Poniżej próba wypisania msg po wywołaniu send spowodowałaby błąd kompilacji, dlatego używamy tej wartości tylko raz.

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>());
}

Iterowanie po odbiorniku

Receiver implementuje IntoIterator. Iterowanie po nim zwraca każdą wartość aż do zamknięcia kanału (czyli usunięcia wszystkich nadajników). To idiomatyczny sposób konsumowania strumienia komunikatów.

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);
    }
}

Wielu producentów dzięki clone

mp w skrócie mpsc oznacza, że możesz mieć wielu nadajników. Sklonuj Sender i przekaż kopię każdemu wątkowi. Odbiornik zbiera wszystkie wartości, dopóki nie zostanie usunięta każda kopia.

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);
    }
}

Zasady zamykania kanału

Pętla odbiornika kończy się automatycznie, gdy zostaną usunięci wszyscy nadajnicy. Jeśli choć jeden Sender pozostanie aktywny, for msg in rx będzie czekać bez końca na kolejne dane. Zawsze poprawnie usuwaj nadajniki albo ograniczaj ich zakres, aby umożliwić zakończenie pętli.

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 do odczytu nieblokującego

recv blokuje działanie, natomiast try_recv natychmiast zwraca Result. Zwraca Ok(value), jeśli komunikat jest gotowy, albo Err, jeśli kanał jest pusty lub rozłączony. Przydaje się to w pętlach zdarzeń, które muszą wykonywać również inne zadania.

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));
    }
}

Wysyłanie typów niestandardowych

Każdy typ implementujący Send może przechodzić przez kanał, w tym własne struktury i enumy. Enumy świetnie nadają się do modelowania różnych rodzajów komunikatów w protokole komunikacji z workerem.

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 i backpressure

mpsc::sync_channel(n) tworzy ograniczony kanał z buforem o rozmiarze n. Gdy bufor jest pełny, send blokuje działanie do czasu zwolnienia miejsca. Zapewnia to mechanizm backpressure, który chroni przed przeciążeniem wolnego konsumenta przez szybkiego producenta.

  • sync_channel(0) to kanał rendezvous: operacje send i recv muszą spotkać się bezpośrednio.
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);
    }
}

Prosty wzorzec workera

Kanały świetnie sprawdzają się we wzorcu producent/konsument: jeden wątek wytwarza elementy pracy, a drugi je odbiera i przetwarza. W tym przypadku wątek główny wytwarza liczby, a wątek roboczy podnosi każdą z nich do kwadratu i odsyła wynik za pośrednictwem drugiego kanału.

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);
    }
}

Szybki test

Sprawdź swoją wiedzę o kanałach mpsc.

Podsumowanie

Opanowali Państwo podstawy kanałów mpsc:

  • mpsc::channel() zwraca parę (Sender, Receiver).
  • send przekazuje wartość do kanału, a recv blokuje działanie, aby ją odebrać.
  • Iterowanie po odbiorniku pobiera komunikaty do momentu, aż wszystkie nadajniki zostaną usunięte.
  • Wywołują Państwo clone dla obiektu Sender, aby utworzyć wielu producentów.
  • try_recv nie blokuje działania, a sync_channel(n) dodaje ograniczone buforowanie i mechanizm backpressure.

Kanały umożliwiają bezpieczne współdzielenie danych między wątkami przez przekazywanie własności zamiast współdzielenia pamięci.

Bezpłatny start

Ucz się Rust dzięki korepetycjom AI — za darmo

Pisz i uruchamiaj kod w przeglądarce, otrzymuj natychmiastową pomoc od korepetytora AI dostępnego 24/7 i kontynuuj naukę w sieci lub w aplikacji.

Kursy
39
Lekcje
144

Często zadawane pytania

Czy lekcja „Kanały mpsc” jest bezpłatna?

Tak — pełny tekst „Kanały mpsc” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Learn Rust Coding, przejdź na CoddyKit PRO. Kurs Learn Rust Coding zawiera 4 lekcji w sumie.

Co nauczysz się w „Kanały mpsc”?

Wysyłanie między wątkami Ćwiczysz Learn Rust Coding z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć Learn Rust Coding?

Nie wymagamy żadnego doświadczenia. Learn Rust Coding w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 1 z 4.

Ile czasu zajmuje lekcja „Kanały mpsc”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji Learn Rust Coding?

Tak. Każda lekcja Learn Rust Coding zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Kanały mpsc
  2. Współdzielenie stanu za pomocą Arc/Mutex
  3. Wątki zakresowe
  4. Kanały Crossbeam
← Powrót do Learn Rust Coding