English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Gestire la concorrenza in modo sicuro ed efficiente è uno degli scopi di Rust, principalmente per risolvere la capacità di resistenza alle elevate prestazioni del server.
Il concetto di concorrenza (concurrent) è che diverse parti del programma eseguono indipendentemente, il che è facile confondere con il concetto di parallelismo (parallel), che enfatizza l'esecuzione "simultanea".
La concorrenza spesso porta al parallelismo.
Questo capitolo spiega i concetti e i dettagli di programmazione relativi alla concorrenza.
Un thread è una parte indipendente di esecuzione di un programma.
La differenza tra il thread e il processo è che il thread è un concetto all'interno del programma, e il programma viene spesso eseguito all'interno di un processo.
In un ambiente con sistema operativo, i processi sono spesso alternati per l'esecuzione, mentre i thread sono schedulati all'interno dei processi dal programma.
Poiché la concorrenza thread può portare a situazioni di parallelismo, gli errori di blocco e di ritardo che possono verificarsi nella concorrenza sono spesso presenti nei programmi con meccanismi di concorrenza.
Per risolvere questi problemi, molti altri linguaggi (come Java, C#) utilizzano software di runtime speciali per coordinare le risorse, ma questo无疑极大地降低了程序的执行效率.
Il linguaggio C/C++ supporta anche la multithreading al livello più basso del sistema operativo, ma il linguaggio stesso e il suo compilatore non dispongono di capacità di rilevamento e prevenzione degli errori paralleli, il che rappresenta una grande pressione per gli sviluppatori, che devono dedicare molte energie per evitare errori.
Rust non si affida all'ambiente di esecuzione, come C/C++.
Ma Rust è progettato con mezzi linguistici che includono il meccanismo di proprietà per eliminare il più possibile gli errori più comuni durante la fase di compilazione, un punto che altri linguaggi non possiedono.
Questo non significa che possiamo essere negligenti nel nostro codice di programmazione, fino ad ora, i problemi causati dalla concorrenza non sono stati completamente risolti in pubblico, è ancora possibile verificarsi errori, cercare di essere il più prudenti possibile durante la programmazione concorrente!
In Rust, viene creato un nuovo processo attraverso la funzione std::thread::spawn:
use std::thread; use std::time::Duration; fn spawn_function() { for i in 0..5 { println!("spawnato thread print {}", i); thread::sleep(Duration::from_millis(1)); } } fn main() { thread::spawn(spawn_function); for i in 0..3 { println!("thread principale print {}", i); thread::sleep(Duration::from_millis(1)); } }
Risultato dell'esecuzione:
thread principale print 0 spawnato thread print 0 thread principale print 1 spawnato thread print 1 thread principale print 2 spawnato thread print 2
Questo risultato potrebbe variare di volta in volta in alcune circostanze, ma in generale è così che viene stampato.
Questo programma ha un thread secondario, lo scopo è stampare 5 righe di testo, il thread principale stampa tre righe di testo, ma è ovvio che con la fine del thread principale, il thread spawnato termina anche, senza completare tutte le stampe.
La funzione std::thread::spawn ha un parametro di funzione senza parametri, ma il seguente modo di scrittura non è il metodo raccomandato, possiamo utilizzare le closure (closures) per passare la funzione come parametro:
use std::thread; use std::time::Duration; fn main() { thread::spawn(|| { for i in 0..5 { println!("spawnato thread print {}", i); thread::sleep(Duration::from_millis(1)); } }); for i in 0..3 { println!("thread principale print {}", i); thread::sleep(Duration::from_millis(1)); } }
Le closure possono essere salvate in variabili o passate come parametri a altre funzioni. Le closure sono equivalenti agli espressioni Lambda in Rust, con il seguente formato:
|parametro1, parametro2, ...| -> tipo di ritorno { // corpo della funzione }
ad esempio:
fn main() { let inc = |num: i32| -> i32 { num + 1 ; println!("inc(5) = {}", inc(5)); }
Risultato dell'esecuzione:
inc(5) = 6
le closure possono omittire la dichiarazione del tipo e utilizzare il meccanismo di giudizio automatico del tipo Rust:
fn main() { let inc = |num| { num + 1 ; println!("inc(5) = {}", inc(5)); }
i risultati non sono cambiati.
use std::thread; use std::time::Duration; fn main() { let handle = thread::spawn(|| { for i in 0..5 { println!("spawnato thread print {}", i); thread::sleep(Duration::from_millis(1)); } }); for i in 0..3 { println!("thread principale print {}", i); thread::sleep(Duration::from_millis(1)); } handle.join().unwrap(); }
Risultato dell'esecuzione:
thread principale print 0 spawnato thread print 0 spawnato thread print 1 thread principale print 1 spawnato thread print 2 thread principale print 2 spawnato thread print 3 spawnato thread print 4
Il metodo join può far sì che il programma si fermi solo dopo che il sotto-thread è terminato.
Questo è un caso comune che si incontra:
use std::thread; fn main() { let s = "hello"; let handle = thread::spawn(|| { println!("{}", s); }); handle.join().unwrap(); }
Provarlo a utilizzare le risorse del funzione corrente nel sotto-thread, questo è sicuramente sbagliato! Poiché il meccanismo di proprietà proibisce la generazione di situazioni pericolose, che distruggerebbero la determinatezza della distruzione delle risorse del meccanismo di proprietà. Possiamo utilizzare la chiave move della clausola per gestirlo:
use std::thread; fn main() { let s = "hello"; let handle = thread::spawn(move || { println!("{}", s); }); handle.join().unwrap(); }
Un degli strumenti principali per la trasmissione di messaggi e la programmazione concorrente in Rust è il canale (channel), che è composto da due parti: un mittente (transmitter) e un ricevitore (receiver).
std::sync::mpsc contiene metodi di trasmissione di messaggi:
use std::thread; use std::sync::mpsc; fn main() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { let val = String::from("hi"); tx.send(val).unwrap(); }); let received = rx.recv().unwrap(); println!("Ricevuto: {}", received); }
Risultato dell'esecuzione:
Ricevuto: hi
Il sotto-thread ha ottenuto il mittente del thread principale tx, ha chiamato il suo metodo send e ha inviato una stringa, quindi il thread principale ha ricevuto attraverso il ricevitore corrispondente rx.