0Pricing
Learn Rust Coding · Lezione

Canali mpsc

Invio tra thread

Canali mpsc è una lezione Learn Rust Coding gratuita su CoddyKit. Questa è la lezione 1 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Learn Rust Coding, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Learn Rust Coding include 4 lezioni in totale.

Che cos'è un canale?

Un channel è un canale unidirezionale per inviare valori da un thread a un altro. La libreria standard di Rust fornisce std::sync::mpsc, dove mpsc significa multiple producer, single consumer (più produttori, un singolo consumatore).

  • La parte Sender inserisce i valori.
  • La parte Receiver estrae i valori.

I canali consentono ai thread di comunicare tramite il passaggio di messaggi invece di condividere direttamente la memoria, evitando così molti data race.

Creare un canale

Chiamate mpsc::channel() per ottenere una tupla di (Sender, Receiver). In questo esempio inviamo un valore da un thread creato appositamente al thread principale.

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

tx.send(value) restituisce un Result: fallisce solo se il receiver è stato eliminato. rx.recv() si blocca finché non arriva un valore e restituisce Err quando non rimane alcun sender.

  • send trasferisce la proprietà del valore nel canale.
  • recv prende la proprietà dall'altra parte.
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);
}

La proprietà attraversa il canale

Poiché send prende il valore per valore, non potete utilizzarlo dopo l'invio. Questa regola verificata in fase di compilazione garantisce che nessun thread mantenga un riferimento obsoleto a dati che ora appartengono a un altro thread.

Di seguito, provare a stampare msg dopo send causerebbe un errore di compilazione, quindi lo utilizziamo una sola volta.

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

Iterare su un Receiver

Un Receiver implementa IntoIterator. Iterandovi sopra ottenete ogni valore finché il canale non viene chiuso (quando tutti i sender vengono eliminati). Questo è il modo idiomatico per consumare un flusso di messaggi.

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

Più produttori con clone

La sigla mp in mpsc indica che potete avere molti sender. Clonate il Sender e date una copia a ogni thread. Il receiver raccoglie tutto finché tutte le copie non vengono eliminate.

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

Semantica della chiusura del canale

Il ciclo del receiver termina automaticamente quando vengono eliminati tutti i sender. Se anche un solo Sender rimane attivo, for msg in rx si blocca per sempre in attesa di altri messaggi. Eliminate sempre i sender o limitatene correttamente l'ambito per consentire la conclusione del ciclo.

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 per letture non bloccanti

recv si blocca, mentre try_recv restituisce immediatamente un Result. Restituisce Ok(value) se un messaggio è pronto oppure Err se il canale è vuoto o disconnesso. È utile nei cicli di eventi che devono continuare a svolgere altre attività.

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

Inviare tipi personalizzati

Qualsiasi tipo che implementa Send può viaggiare attraverso un canale, incluse le vostre struct e i vostri enum. Gli enum sono ottimi per modellare diversi tipi di messaggio in un protocollo per worker.

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

mpsc::sync_channel(n) crea un canale limitato con una dimensione del buffer pari a n. Quando il buffer è pieno, send si blocca finché non si libera spazio. In questo modo ottenete il backpressure, impedendo a un produttore veloce di sovraccaricare un consumatore lento.

  • sync_channel(0) è un canale rendezvous: send e recv devono incontrarsi.
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);
    }
}

Un semplice modello worker

I canali sono ideali per il modello produttore/consumatore: un thread produce elementi di lavoro, mentre un altro li consuma e li elabora. Qui il thread principale produce numeri e un worker eleva al quadrato ciascuno di essi, comunicando poi il risultato tramite un secondo canale.

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

Verifica rapida

Verificate la vostra comprensione dei canali mpsc.

Riepilogo

Avete imparato i fondamenti dei canali mpsc:

  • mpsc::channel() restituisce una coppia (Sender, Receiver).
  • send trasferisce un valore nel canale; recv si blocca per estrarlo.
  • Iterare su un receiver consuma i messaggi finché tutti i sender non vengono eliminati.
  • Clonate il Sender per avere più produttori.
  • try_recv non è bloccante; sync_channel(n) aggiunge un backpressure limitato.

I canali consentono ai thread di condividere i dati in sicurezza trasferendo la proprietà invece di condividere la memoria.

Domande Frequenti

La lezione «Canali mpsc» è gratuita?

Sì — il testo completo di «Canali mpsc» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Learn Rust Coding, passa a CoddyKit PRO. Il corso Learn Rust Coding include 4 lezioni in totale.

Cosa imparerò in «Canali mpsc»?

Invio tra thread Eserciti Learn Rust Coding con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare Learn Rust Coding?

Non è richiesta alcuna esperienza precedente. Learn Rust Coding su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 1 di 4.

Quanto tempo richiede la lezione «Canali mpsc»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione Learn Rust Coding?

Sì. Ogni lezione Learn Rust Coding include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Canali mpsc
  2. Condivisione dello stato con Arc/Mutex
  3. Thread con ambito
  4. Canali Crossbeam
← Torna a Learn Rust Coding