We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Fearless Concurrency: Threads, Channels, Shared State step 9 of 24
Arc: shared ownership across threads
Share one allocation between workers detached threads, sum a disjoint slice
in each, and report the total and the reference count afterwards.
pub fn share_and_reduce(data: Vec<i64>, workers: usize) -> (i64, usize)
Wrap data in an Arc, hand a clone to each worker, spawn with
thread::spawn (not scope — this problem is about ownership), join
everything, and return (total, Arc::strong_count(&shared)).
Treat workers == 0 as 1. Use the balanced split from earlier:
base = len / workers, extra = len % workers, first extra slices one
longer. Empty input gives (0, 1).
Say the count out loud before you write it
The second element of the tuple is always 1, for every input, every worker count, every run. Work out why before you read on — it is the whole model in one number.
Because: you created one Arc, cloned it workers times, each clone was
moved into a thread, each thread ended, and when a thread ends its captured
values are dropped, which decrements the count. By the time every join
returns, the only handle left is the one you are holding. strong_count
reads 1.
This is not a coincidence you should lean on generally. Reading
strong_count while other threads are alive is a race — the value is stale
the instant you get it, since another thread can clone or drop between the
read and your use of it. It is deterministic here only because every join
has completed. That constraint is the reason it is a fair test case rather
than a trick.
What Arc is
Arc<T> — atomically reference counted — is shared ownership. The value
lives on the heap next to a counter; clone bumps the counter and hands back
another handle; drop decrements; the last one out frees the value. Every
handle derefs to &T.
It is Rc with the counter updated by atomic instructions instead of plain
ones, which is precisely why Arc: Send + Sync (given T: Send + Sync) and
Rc is neither. That is the whole difference. Arc is not “the thread-safe
smart pointer” in any deeper sense — see the next problem for what it is
emphatically not.
The starter’s error
error[E0382]: use of moved value: `shared`
|
| for i in 0..workers {
| ------------------- inside of this loop
| handles.push(thread::spawn(move || shared[start..end]...));
| ^^^^^^^ ------ use occurs due to use in closure
| |
| value moved here, in previous iteration of loop
move moved the Arc handle itself into the first closure. On the second
iteration there is nothing left. The fix is not to remove move — the thread
genuinely needs to own something — it is to give each thread its own
handle:
let mine = Arc::clone(&shared);
handles.push(thread::spawn(move || { ... mine ... }));
Cloning an Arc does not clone the data. It is one atomic increment and a
pointer copy, whatever T is. The Vec<i64> is allocated exactly once no
matter how many workers you spawn.
Arc::clone(&x) versus x.clone()
You will see the explicit form far more often, and the reason given is
readability: Arc::clone(&shared) says “a new handle, cheap” at a glance,
while shared.clone() looks like it might be duplicating a Vec<i64>.
Be aware this is a convention, not a rule. clippy::clone_on_ref_ptr
would enforce it, but it is in the restriction group — off by default, and
it will not fire on this problem either way. A vocal minority now argues
.clone() is fine and that the type is what tells you the cost. Pick one and
be consistent; do not tell a colleague the compiler requires it.
One lint that will bite you if you get clever
A tempting way to make workers handles at once:
let handles = vec![Arc::new(data); workers];
error: initializing a reference-counted pointer in `vec![elem; len]`
= note: each element will point to the same `Arc` instance
clippy::rc_clone_in_vec_init is warn-by-default and therefore a failure
under this gate. vec![elem; n] evaluates elem once and clones it, so this
is n handles to one allocation — occasionally what you want, and almost
never what someone writing that line meant. Clone in a loop, or hoist the
Arc::new to its own binding first so the intent is visible.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.