Arc în Rust

Pointeri inteligenți: Box, Rc, Arc Rust 1.0 (strong_count: 1.15)

Varianta lui Rc care poate fi împărțită între fire: contorul de proprietari este actualizat atomic.

Sintaxă
use std::sync::Arc;

fn new(data: T) -> Arc<T>
fn clone(&self) -> Arc<T>
fn strong_count(this: &Arc<T>) -> usize

Ce face Arc

Arc<T> (atomically reference counted) face același lucru ca Rc: mai mulți proprietari pentru aceeași valoare, eliberată când dispare ultimul. Diferența este că actualizează contorul cu operații atomice, deci poate fi trimis în alte fire.

Modelul obișnuit: creezi Arc::new(date), faci un Arc::clone(&date) pentru fiecare fir și îl muți în fir cu move. Datele pot fi doar citite. Pentru modificare din mai multe fire, folosește Arc<Mutex<T>>, Arc<RwLock<T>> sau tipuri atomice.

Operațiile atomice sunt puțin mai lente, așa că într-un singur fir rămâi la Rc.

Parametri

Parametru Descriere
data Valoarea împărțită (la new).

Ce returnează

new și clone returnează Arc<T>; strong_count returnează numărul de proprietari.

Exemple

Exemplul 1: Date comune pentru mai multe fire

Fiecare fir primește propriul Arc spre aceeași listă de prețuri. Rezultatele vin prin join, în ordinea firelor.

use std::sync::Arc;
use std::thread;

fn main() {
    let preturi = Arc::new(vec![12.5, 30.0, 7.25, 50.0]);

    let mut fire = Vec::new();
    for (i, cota_tva) in [0.09, 0.19].into_iter().enumerate() {
        let preturi = Arc::clone(&preturi);
        fire.push(thread::spawn(move || {
            let total: f64 = preturi.iter().map(|p| p * (1.0 + cota_tva)).sum();
            format!("firul {i}: total cu TVA {:.0}% = {total:.2} lei", cota_tva * 100.0)
        }));
    }
    for f in fire {
        println!("{}", f.join().unwrap());
    }
    println!("proprietari la final: {}", Arc::strong_count(&preturi));
}

Rezultat

firul 0: total cu TVA 9% = 108.73 lei
firul 1: total cu TVA 19% = 118.70 lei
proprietari la final: 1

Exemplul 2: strong_count în timp ce firele lucrează

Cu thread::scope știm sigur când se termină firele. În interior, fiecare fir ține un Arc.

use std::sync::Arc;
use std::thread;

fn main() {
    let mesaj = Arc::new(String::from("Salut din Iași"));
    println!("înainte: {}", Arc::strong_count(&mesaj));

    let copie1 = Arc::clone(&mesaj);
    let copie2 = Arc::clone(&mesaj);
    println!("cu două copii: {}", Arc::strong_count(&mesaj));

    thread::scope(|s| {
        s.spawn(move || copie1.len());
        s.spawn(move || copie2.len());
    });
    println!("după fire: {}", Arc::strong_count(&mesaj));
}

Rezultat

înainte: 1
cu două copii: 3
după fire: 1

Greșeli frecvente

  • Scrii move fără să clonezi înainte Arc-ul: primul fir mută Arc-ul original și al doilea nu mai are ce folosi (use of moved value).
  • Încerci să modifici datele dintr-un Arc<Vec<T>>: nu se poate. Pune un Mutex înăuntru.

Comenzi înrudite

  • Rc: Pointer cu numărarea referințelor: mai mulți proprietari împart aceeași valoare, eliberată când dispare ultimul.
  • Mutex: Protejează o valoare folosită din mai multe fire: lock() așteaptă până când firul curent este singurul cu acces.
  • thread::spawn: Pornește un fir de execuție nou care rulează funcția dată; join() așteaptă terminarea lui și îi dă rezultatul.
  • AtomicUsize: Număr întreg care poate fi citit și modificat din mai multe fire fără Mutex, cu operații atomice.