0Pricing
Learn Rust Coding · Lekcja

Współdzielenie stanu za pomocą Arc/Mutex

Bezpieczne współdzielone dane

Współdzielenie stanu za pomocą Arc/Mutex to bezpłatna lekcja Learn Rust Coding na CoddyKit. To lekcja 2 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.

Dlaczego współdzielony stan jest trudny

Czasami przekazywanie komunikatów nie wystarcza i wiele wątków rzeczywiście musi odczytywać oraz zapisywać te same dane. Rust nie pozwala współdzielić między wątkami modyfikowalnej wartości bez ochrony, ponieważ groziłoby to wyścigiem danych.

Łączone narzędzia to:

  • Arc do współdzielenia własności między wątkami.
  • Mutex do bezpiecznej, wyłącznej modyfikacji.

Rc nie jest bezpieczny wątkowo

Rc zapewnia współdzieloną własność, ale tylko w obrębie jednego wątku. Jego licznik odwołań nie jest synchronizowany, dlatego kompilator nie pozwala przesyłać go między wątkami. Do współdzielenia między wątkami potrzebują Państwo Arc (Atomically Reference Counted).

Arc działa podobnie jak Rc, ale aktualizuje licznik za pomocą operacji atomowych, dzięki czemu jego klony są bezpieczne między wątkami.

use std::sync::Arc;

fn main() {
    let data = Arc::new(vec![1, 2, 3]);
    let clone1 = Arc::clone(&data);
    println!("original: {:?}", data);
    println!("clone:    {:?}", clone1);
    println!("count:    {}", Arc::strong_count(&data));
}

Mutex zapewnia wyłączny dostęp

Mutex opakowuje dane i gwarantuje, że w danym momencie uzyskuje do nich dostęp tylko jeden wątek. Wywołują Państwo .lock(), aby otrzymać obiekt MutexGuard, który dereferencjonuje się do wewnętrznej wartości. Blokada jest automatycznie zwalniana, gdy obiekt guard wyjdzie poza zakres.

use std::sync::Mutex;

fn main() {
    let m = Mutex::new(5);
    {
        let mut num = m.lock().unwrap();
        *num += 10;
    } // guard dropped here, lock released
    println!("value = {:?}", m.lock().unwrap());
}

Łączenie Arc i Mutex

Aby współdzielić między wątkami modyfikowalne dane, należy opakować je jako Arc>:

  • Arc pozwala wielu wątkom posiadać uchwyt do tych samych danych.
  • Mutex pozwala każdemu wątkowi bezpiecznie je modyfikować, po jednym wątku naraz.

Przed utworzeniem wątków należy sklonować obiekt Arc dla każdego z nich.

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let counter = Arc::new(Mutex::new(0));
    let c = Arc::clone(&counter);
    let handle = thread::spawn(move || {
        let mut n = c.lock().unwrap();
        *n += 1;
    });
    handle.join().unwrap();
    println!("counter = {}", *counter.lock().unwrap());
}

Współdzielony licznik w wielu wątkach

Klasyczny przykład: dziesięć wątków zwiększa współdzielony licznik. Każdy wątek ma własny klon Arc i blokuje Mutex, aby dodać jeden. Po dołączeniu wszystkich wątków suma wynosi dokładnie 10 i nie występuje wyścig danych.

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];
    for _ in 0..10 {
        let c = Arc::clone(&counter);
        handles.push(thread::spawn(move || {
            let mut num = c.lock().unwrap();
            *num += 1;
        }));
    }
    for h in handles {
        h.join().unwrap();
    }
    println!("Result: {}", *counter.lock().unwrap());
}

Zakres blokady ma znaczenie

Obiekt MutexGuard utrzymuje blokadę do momentu usunięcia. Przetrzymywanie go podczas powolnych operacji blokuje inne wątki. Sekcje krytyczne powinny być krótkie: zablokować, zmodyfikować, zwolnić. Umieszczenie blokady w małym bloku gwarantuje jej zwolnienie przed rozpoczęciem dodatkowego przetwarzania.

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let log = Arc::new(Mutex::new(Vec::new()));
    let mut handles = vec![];
    for i in 0..3 {
        let l = Arc::clone(&log);
        handles.push(thread::spawn(move || {
            {
                let mut v = l.lock().unwrap();
                v.push(i);
            } // released quickly
        }));
    }
    for h in handles { h.join().unwrap(); }
    let mut result = log.lock().unwrap().clone();
    result.sort();
    println!("{:?}", result);
}

Zakleszczenia: rzeczywiste zagrożenie

Zakleszczenie występuje, gdy dwa wątki posiadają blokady potrzebne drugiemu wątkowi i oba czekają w nieskończoność. Rust zapobiega wyścigom danych, ale nie zakleszczeniom. Należy ich unikać, zawsze blokując wiele mutexów w tej samej kolejności i utrzymując blokady przez krótki czas.

Należy również unikać dwukrotnego blokowania tego samego obiektu Mutex w jednym wątku; standardowy Mutex nie jest reentrantny.

Zatrucie po panice wątku

Jeśli wątek wywoła panikę podczas utrzymywania blokady, obiekt Mutex zostaje zatruty. Późniejsze wywołania .lock() zwracają Err, dzięki czemu wiadomo, że dane mogą być niespójne. Jeśli uznają Państwo, że dane nadal nadają się do użycia, można odzyskać wewnętrzny obiekt guard za pomocą into_inner() wywołanego na błędzie.

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let data = Arc::new(Mutex::new(0));
    let d = Arc::clone(&data);
    let _ = thread::spawn(move || {
        let mut g = d.lock().unwrap();
        *g = 7;
        panic!("boom"); // poisons the mutex
    }).join();
    match data.lock() {
        Ok(g) => println!("ok: {}", *g),
        Err(poisoned) => println!("recovered: {}", *poisoned.into_inner()),
    }
}

RwLock dla wielu czytelników

Gdy odczytów jest znacznie więcej niż zapisów, RwLock może działać szybciej niż Mutex. Umożliwia on wielu jednoczesnym czytelnikom albo jednemu zapisującemu. Do współdzielonego dostępu należy używać .read(), a do wyłącznego dostępu — .write().

use std::sync::{Arc, RwLock};
use std::thread;

fn main() {
    let config = Arc::new(RwLock::new(String::from("v1")));
    let reader = Arc::clone(&config);
    let r = thread::spawn(move || {
        let val = reader.read().unwrap();
        println!("read: {}", *val);
    });
    r.join().unwrap();
    {
        let mut w = config.write().unwrap();
        *w = String::from("v2");
    }
    println!("final: {}", *config.read().unwrap());
}

Atomiki dla prostych liczników

W przypadku pojedynczego licznika całkowitoliczbowego pełny Mutex jest przesadą. Typy takie jak AtomicUsize oferują aktualizacje bez blokad za pomocą metod takich jak fetch_add. Należy opakować je w Arc, aby współdzielić je między wątkami. Trzeba wybrać obiekt Ordering; SeqCst jest najprostszym bezpiecznym ustawieniem domyślnym.

use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread;

fn main() {
    let counter = Arc::new(AtomicUsize::new(0));
    let mut handles = vec![];
    for _ in 0..5 {
        let c = Arc::clone(&counter);
        handles.push(thread::spawn(move || {
            c.fetch_add(1, Ordering::SeqCst);
        }));
    }
    for h in handles { h.join().unwrap(); }
    println!("count = {}", counter.load(Ordering::SeqCst));
}

Wybór właściwego narzędzia

Krótki przewodnik po współdzielonym stanie:

  • Channel: przekazywanie własności w stylu potoku.
  • Arc<Mutex>: współdzielona modyfikowalna struktura, z mieszanym odczytem i zapisem.
  • Arc<RwLock>: współdzielone dane, z których głównie się odczytuje.
  • Atomics: pojedyncze liczniki lub flagi typów prymitywnych.

Należy wybierać najprostsze narzędzie, które pasuje do problemu; po blokady warto sięgać dopiero wtedy, gdy przekazywanie komunikatów nie odwzorowuje go dobrze.

Szybki test

Sprawdź swoją wiedzę o współdzielonym stanie.

Podsumowanie

Dowiedzieli się Państwo, jak bezpiecznie współdzielić stan między wątkami:

  • Arc zapewnia bezpieczne wątkowo współdzielenie własności, a Rc nie.
  • Mutex zapewnia wyłączną modyfikację za pośrednictwem obiektu guard, który automatycznie zwalnia blokadę.
  • Arc<Mutex<T>> to standardowy wzorzec współdzielenia modyfikowalnych danych.
  • Należy ograniczać zakres blokad oraz uważać na zakleszczenia i zatrucie.
  • RwLock sprawdza się przy danych często odczytywanych, a atomiki — przy prostych licznikach.

Często zadawane pytania

Czy lekcja „Współdzielenie stanu za pomocą Arc/Mutex” jest bezpłatna?

Tak — pełny tekst „Współdzielenie stanu za pomocą Arc/Mutex” 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 „Współdzielenie stanu za pomocą Arc/Mutex”?

Bezpieczne współdzielone dane Ć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 2 z 4.

Ile czasu zajmuje lekcja „Współdzielenie stanu za pomocą Arc/Mutex”?

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