0Pricing
Learn Rust Coding · Lezione

Concorrenza senza timori con i thread

Approfondisca i primitivi di Rust per la programmazione concorrente, inclusi thread e passaggio di messaggi, garantendo l’assenza di data race.

Concorrenza senza timori con i thread è una lezione Learn Rust Coding gratuita su CoddyKit. Questa è la lezione 3 di 3. 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 3 lezioni in totale.

Introduzione alla concorrenza

Benvenuto nella concorrenza senza preoccupazioni con i thread! In questa lezione imparerà a fare eseguire più operazioni contemporaneamente ai programmi Rust.

La concorrenza consiste nell'eseguire più calcoli apparentemente nello stesso momento. È essenziale per creare applicazioni reattive e sfruttare i moderni processori multi-core.

  • I thread sono unità di esecuzione leggere all'interno di un programma.
  • Ogni thread può eseguire una parte distinta del codice.
  • Il sistema di ownership di Rust aiuta a prevenire i comuni bug di concorrenza.

Creazione del primo thread

Rust mette a disposizione std::thread::spawn per creare nuovi thread. Gli si passa una closure (una funzione anonima) contenente il codice che il nuovo thread deve eseguire.

Provi a eseguire questo esempio per vedere i thread in azione:

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

Attendere i thread con `join`

Nell'esempio precedente, il thread creato potrebbe non terminare prima che il thread principale esca. Questo accade perché, per impostazione predefinita, il thread principale non attende i thread creati.

Per assicurarsi che un thread creato completi il proprio lavoro, si usa il metodo join() sul relativo JoinHandle. Questo blocca il thread corrente finché il thread sottoposto a join non termina.

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

Spostare i dati nei thread

Quando si usano variabili dell'ambiente all'interno di una closure spawn, entrano in gioco le regole di ownership di Rust. Per impostazione predefinita, le closure cercano di prendere in prestito le variabili.

Se il thread creato sopravvive all'ambito del thread principale in cui la variabile è stata definita, ciò può causare un riferimento pendente. Per risolvere il problema, si usi la parola chiave move prima dei parametri della closure.

  • move obbliga la closure ad acquisire l'ownership delle variabili catturate.
  • In questo modo i dati rimangono validi per l'intera durata del nuovo 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.");
}

Comunicazione tramite passaggio di messaggi

Sebbene in Rust sia possibile condividere direttamente i dati tra thread (stato condiviso), è necessaria un'attenta sincronizzazione (ad esempio con Mutex e Arc, trattati in altre lezioni).

Un approccio più sicuro e spesso più semplice alla concorrenza è il passaggio di messaggi. I thread comunicano inviandosi messaggi a vicenda, evitando l'accesso diretto alla memoria condivisa.

  • Un thread invia i dati.
  • Un altro thread riceve i dati.
  • La libreria standard di Rust fornisce std::sync::mpsc a questo scopo.

Configurazione di un canale `mpsc`

std::sync::mpsc è l'acronimo di Multiple Producer, Single Consumer. Significa che molti thread possono inviare messaggi, ma un solo thread può riceverli.

Per creare un canale, si chiama mpsc::channel(). Questo restituisce una tupla contenente:

  • Un Sender (tx): utilizzato per inviare messaggi.
  • Un Receiver (rx): utilizzato per ricevere messaggi.

Vediamo come crearne uno e inviare un semplice messaggio.

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

Invio di più messaggi

È possibile inviare più messaggi attraverso lo stesso canale. Receiver dispone di metodi per gestire questa situazione:

  • rx.recv(): blocca il thread corrente finché non riceve un messaggio. Restituisce un Result.
  • rx.try_recv(): non bloccante. Restituisce immediatamente un Result, contenente un messaggio oppure un errore se non sono disponibili messaggi.
  • È anche possibile iterare direttamente su Receiver per ottenere i messaggi finché il sender non viene chiuso.
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!");
}

Più produttori, un singolo consumatore

La sigla "MP" in mpsc significa Multiple Producers. È possibile clonare un Sender per consentire a più thread di inviare messaggi allo stesso Receiver.

Ogni Sender clonato condivide l'accesso allo stesso canale, consentendo l'invio concorrente di messaggi da thread diversi.

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

Concorrenza senza preoccupazioni con Rust

Il sistema di ownership e dei tipi di Rust è fondamentale per ottenere una concorrenza senza preoccupazioni. Applicando le regole in fase di compilazione, Rust previene bug comuni di concorrenza come le race condition sui dati.

  • Ownership impedisce la presenza di più riferimenti mutabili agli stessi dati.
  • La parola chiave move garantisce che i dati vengano trasferiti in sicurezza a un nuovo thread.
  • Il passaggio di messaggi (mpsc) evita completamente la memoria condivisa, rendendo sicura la comunicazione per progettazione.

Questi meccanismi consentono di scrivere codice concorrente con sicurezza, sapendo che il compilatore rileverà molti potenziali problemi.

Verifica dei concetti di concorrenza

Quali delle seguenti affermazioni sui primitivi di concorrenza di Rust sono VERE?

Riepilogo: thread e passaggio di messaggi

Ottimo lavoro! Ha appreso i fondamenti della concorrenza senza preoccupazioni in Rust:

  • Si creano nuovi thread usando std::thread::spawn.
  • Si usa JoinHandle::join() per attendere il completamento di un thread.
  • La parola chiave move trasferisce in sicurezza l'ownership dei dati a un thread.
  • std::sync::mpsc fornisce un solido sistema di passaggio di messaggi per la comunicazione tra thread.
  • Le regole di ownership di Rust prevengono le race condition sui dati in fase di compilazione, rendendo più sicura la programmazione concorrente.

Questa lezione fornisce solide basi per creare applicazioni reattive ed efficienti. Continui a esercitarsi!

Domande Frequenti

La lezione «Concorrenza senza timori con i thread» è gratuita?

Sì — il testo completo di «Concorrenza senza timori con i thread» è 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 3 lezioni in totale.

Cosa imparerò in «Concorrenza senza timori con i thread»?

Approfondisca i primitivi di Rust per la programmazione concorrente, inclusi thread e passaggio di messaggi, garantendo l’assenza di data race. 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 3 di 3.

Quanto tempo richiede la lezione «Concorrenza senza timori con i thread»?

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. Smart pointer Box, Rc e Arc
  2. Mutabilità interna: RefCell e Cell
  3. Concorrenza senza timori con i thread
← Torna a Learn Rust Coding