Beztroska współbieżność z wątkami
Poznają Państwo mechanizmy Rust do programowania współbieżnego, w tym wątki i przesyłanie komunikatów, zapewniające brak wyścigów danych.
Beztroska współbieżność z wątkami to bezpłatna lekcja Learn Rust Coding na CoddyKit. To lekcja 3 z 3. 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 3 lekcji w sumie.
Wprowadzenie do współbieżności
Witamy w lekcji Bezstresowa współbieżność z wątkami! W tej lekcji nauczą się Państwo, jak sprawić, aby programy w języku Rust wykonywały wiele zadań jednocześnie.
Współbieżność oznacza wykonywanie wielu obliczeń pozornie w tym samym czasie. Ma kluczowe znaczenie dla responsywnych aplikacji i wykorzystywania nowoczesnych procesorów wielordzeniowych.
- Wątki to lekkie jednostki wykonania w obrębie programu.
- Każdy wątek może wykonywać odrębną część kodu.
- System własności języka Rust pomaga zapobiegać typowym błędom związanym ze współbieżnością.
Tworzenie pierwszego wątku
Rust udostępnia std::thread::spawn do tworzenia nowych wątków. Przekazuje się do niego domknięcie (funkcję anonimową) zawierające kod, który powinien wykonać nowy wątek.
Proszę uruchomić ten przykład, aby zobaczyć wątki w działaniu:
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));
}
}Oczekiwanie na wątki za pomocą `join`
W poprzednim przykładzie utworzony wątek mógł nie zakończyć działania przed zakończeniem wątku głównego. Dzieje się tak, ponieważ wątek główny domyślnie nie czeka na utworzone wątki.
Aby mieć pewność, że utworzony wątek zakończy swoją pracę, używamy metody join() na obiekcie JoinHandle. Wstrzymuje ona bieżący wątek do momentu zakończenia wątku, na który oczekujemy.
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!");
}Przenoszenie danych do wątków
Gdy używają Państwo zmiennych ze środowiska wewnątrz domknięcia przekazywanego do spawn, zaczynają obowiązywać reguły własności języka Rust. Domknięcia domyślnie próbują pożyczać zmienne.
Jeśli utworzony wątek będzie działał dłużej niż zakres wątku głównego, w którym zdefiniowano zmienną, może to doprowadzić do powstania wiszącego odwołania. Aby temu zapobiec, należy umieścić słowo kluczowe move przed parametrami domknięcia.
movewymusza przejęcie przez domknięcie własności przechwyconych zmiennych.- Gwarantuje to, że dane będą prawidłowe przez cały czas życia nowego wątku.
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.");
}Komunikacja za pomocą przekazywania komunikatów
Chociaż bezpośrednie współdzielenie danych między wątkami (współdzielony stan) jest w języku Rust możliwe, wymaga starannej synchronizacji (np. za pomocą Mutex i Arc, omawianych w innych lekcjach).
Bezpieczniejszym i często prostszym podejściem do współbieżności jest przekazywanie komunikatów. Wątki komunikują się, wysyłając sobie nawzajem komunikaty, bez bezpośredniego dostępu do współdzielonej pamięci.
- Jeden wątek wysyła dane.
- Inny wątek odbiera dane.
- Biblioteka standardowa języka Rust udostępnia do tego
std::sync::mpsc.
Konfigurowanie kanału `mpsc`
std::sync::mpsc oznacza Multiple Producer, Single Consumer (wielu nadawców, jeden odbiorca). Oznacza to, że wiele wątków może wysyłać komunikaty, ale tylko jeden wątek może je odbierać.
Aby utworzyć kanał, wywołuje się mpsc::channel(). Zwraca on krotkę zawierającą:
- Obiekt Sender (
tx): służący do wysyłania komunikatów. - Obiekt Receiver (
rx): służący do odbierania komunikatów.
Zobaczmy, jak utworzyć kanał i wysłać prosty komunikat.
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);
}Wysyłanie wielu komunikatów
Za pośrednictwem tego samego kanału można wysyłać wiele komunikatów. Obiekt Receiver udostępnia metody do obsługi takiej sytuacji:
rx.recv(): Wstrzymuje bieżący wątek do momentu odebrania komunikatu. Zwraca obiekt typuResult.rx.try_recv(): Nie blokuje wykonania. Natychmiast zwraca obiekt typuResult— zawierający komunikat albo błąd, jeśli żaden komunikat nie jest dostępny.- Można również bezpośrednio iterować po obiekcie
Receiver, aby odbierać komunikaty do momentu zamknięcia kanału przez nadawcę.
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!");
}Wielu nadawców, jeden odbiorca
Litery „MP” w mpsc oznaczają Multiple Producers (wielu nadawców). Można sklonować obiekt Sender, aby wiele wątków mogło wysyłać komunikaty do tego samego obiektu Receiver.
Każdy sklonowany obiekt Sender współdzieli dostęp do tego samego kanału, umożliwiając jednoczesne wysyłanie komunikatów z różnych wątków.
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);
}Bezstresowa współbieżność z Rustem
System własności i system typów języka Rust mają kluczowe znaczenie dla osiągnięcia bezstresowej współbieżności. Egzekwując reguły w czasie kompilacji, Rust zapobiega typowym błędom związanym ze współbieżnością, takim jak wyścigi danych.
- Własność zapobiega istnieniu wielu zmiennych odwołań do tych samych danych.
- Słowo kluczowe
movezapewnia bezpieczne przekazanie danych do nowego wątku. - Przekazywanie komunikatów (
mpsc) całkowicie eliminuje współdzieloną pamięć, dzięki czemu komunikacja jest bezpieczna z założenia.
Mechanizmy te pozwalają pisać kod współbieżny z przekonaniem, że kompilator wykryje wiele potencjalnych problemów.
Sprawdzenie pojęć związanych ze współbieżnością
Które z poniższych stwierdzeń dotyczących mechanizmów współbieżności języka Rust są PRAWDZIWE?
Powtórzenie: wątki i przekazywanie komunikatów
Świetnie! Poznali Państwo podstawy bezstresowej współbieżności w języku Rust:
- Nowe wątki tworzymy za pomocą
std::thread::spawn. - Używamy
JoinHandle::join(), aby zaczekać na zakończenie wątku. - Słowo kluczowe
movebezpiecznie przekazuje własność danych do wątku. std::sync::mpscudostępnia solidny system przekazywania komunikatów między wątkami.- Reguły własności języka Rust zapobiegają wyścigom danych w czasie kompilacji, zwiększając bezpieczeństwo programowania współbieżnego.
Ta lekcja stanowi solidną podstawę do tworzenia responsywnych i wydajnych aplikacji. Proszę dalej ćwiczyć!
Często zadawane pytania
Czy lekcja „Beztroska współbieżność z wątkami” jest bezpłatna?
Tak — pełny tekst „Beztroska współbieżność z wątkami” 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 3 lekcji w sumie.
Co nauczysz się w „Beztroska współbieżność z wątkami”?
Poznają Państwo mechanizmy Rust do programowania współbieżnego, w tym wątki i przesyłanie komunikatów, zapewniające brak wyścigów danych. Ć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 3 z 3.
Ile czasu zajmuje lekcja „Beztroska współbieżność z wątkami”?
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
- Inteligentne wskaźniki Box, Rc i Arc
- Wewnętrzna mutowalność: RefCell, Cell
- Beztroska współbieżność z wątkami