RustArena

Step 3 of 7

Arc: shared ownership across threads

A channel moves data from one thread to another. Sometimes many threads need to read the same data: a price list, a config, a dictionary. Cloning it for every thread wastes memory, and scoped threads only work when all threads finish inside one function. For threads started with thread::spawn, you share data with Arc<T>.

You met Arc briefly in the last module. It's the thread-safe version of Rc (module 29): Arc::new(value) puts the value on the heap next to a reference count. Arc::clone(&a) makes another pointer to the same value and adds one to the count; no data is copied. When the last Arc is dropped, the value is dropped. The count is updated with atomic CPU instructions, so Arc<T> can be sent to and shared between threads (as long as T itself is Send and Sync).

use std::collections::HashMap;
use std::sync::Arc;
use std::thread;

fn main() {
    let prices: HashMap<&str, u32> = HashMap::from([("kelp", 3), ("shell", 12), ("pearl", 250)]);
    let prices = Arc::new(prices);
    let orders = vec![vec!["kelp", "kelp", "shell"], vec!["pearl"], vec!["shell", "kelp"]];

    let mut handles = Vec::new();
    for order in orders {
        let prices = Arc::clone(&prices);
        handles.push(thread::spawn(move || order.iter().map(|item| prices[item]).sum::<u32>()));
    }
    for (i, handle) in handles.into_iter().enumerate() {
        println!("order {i}: {}", handle.join().unwrap());
    }
    println!("owners of the price table now: {}", Arc::strong_count(&prices));
}
order 0: 18
order 1: 250
order 2: 15
owners of the price table now: 1

Arc is read-only

Arc<T> hands out shared references (&T) only. Several threads hold it at once, so letting one of them change the value would be a data race:

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

fn main() {
    let stock = Arc::new(vec![1, 2, 3]);
    let for_worker = Arc::clone(&stock);
    let handle = thread::spawn(move || {
        for_worker.push(4);
    });
    handle.join().unwrap();
    println!("{:?}", stock);
}
error[E0596]: cannot borrow data in an `Arc` as mutable

To change shared data, put a lock inside the Arc: Arc<Mutex<T>>, the next step.

Common mistakes

Your task

Three warehouse requests are checked against one shared stock table, each in its own spawned thread. Wrap stock in an Arc, give each thread its own clone, and have each thread return its line. Join in order and print:

2 x helmet: ok
5 x shield: short by 2
1 x sword: unknown item

A request is ok when the stock has at least the asked quantity, short by N when it has less, and unknown item when the item isn't in the table at all.

Challenge 2

Fill in the two blanks: put the menu behind an Arc, and give the worker its own pointer to it. The program should print the worker counted 3 dishes and then main still has 3 dishes.

Challenge 3

Write fn count_matches(lines: Arc<Vec<String>>, patterns: Vec<String>) -> Vec<usize>. Spawn one thread per pattern with thread::spawn (not a scope); each counts how many lines contain its pattern. Share the lines through the Arc (no copying of the lines) and return the counts in the same order as patterns.

Previous: Multiple producers: cloning the Sender Next: Mutex: one writer at a time