We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Fearless Concurrency: Threads, Channels, Shared State step 11 of 24
Mutex and MutexGuard: unlocking with RAII
Repair the final ownership step of a shared-counter function:
pub fn concurrent_counter(increments: Vec<u64>, workers: usize) -> u64
The starter divides the increments into chunks, adds them through one
Arc<Mutex<u64>>, and joins all worker threads. Return the sum by taking
ownership of the final counter, without locking it again.
Treat workers == 0 as one. Empty input returns zero. Inputs are small
enough for this thread-per-chunk design, and the total fits in u64.
Some totals exceed u32::MAX, so keep the specified integer type.
| Increments | Workers | Result |
|---|---|---|
[2, 3, 4] |
2 | 9 |
[2, 3, 4] |
0 | 9 |
[] |
5 | 0 |
[4000000000, 1000000000] |
2 | 5000000000 |
The worker count is a limit for chunking, not a requirement to start idle
threads. The starter uses chunks of size
increments.len().div_ceil(workers.max(1)).max(1). This can create fewer
threads than requested, and creates none for empty input.
A guard controls access to the protected value
Mutex<T> stores the data with the synchronization state. A successful
lock operation returns a guard that permits access to the data:
use std::sync::Mutex;
let counter = Mutex::new(0u64);
{
let mut guard = counter.lock().unwrap();
*guard += 1;
} // Dropping the guard releases the lock.
assert_eq!(*counter.lock().unwrap(), 1);
This is RAII: cleanup belongs to a value's destructor. Normal scope exit, early return and panic unwinding drop the guard. Aborting the process does not unwind. Deliberately forgetting a guard can prevent its destructor from running and leave the lock held.
A guard is not released just because it has no further reads in the source.
Its drop scope matters. Use a small block or drop(guard) when you need to
release it before doing other work. The starter's temporary guard is dropped
at the end of each addition statement, so it locks once per increment.
Why lock returns a Result
A panic during a critical section may leave protected data inconsistent.
Poisoning reports that possibility. It is advisory and does not detect every
panic. Calling expect on a poisoned lock panics; recovering safely requires
deciding whether the protected invariants still hold.
The exercise assumes successful worker execution. If a worker panics,
join().expect(...) reports failure instead of returning a partial total.
Unwrap the Arc before consuming the mutex
The starter ends with counter.into_inner(). Method lookup finds
Mutex::into_inner through Arc's dereference, but that method consumes
the mutex. Dereferencing a shared ownership handle cannot move its contents
out, so the starter reports E0507.
Use the associated function Arc::into_inner(counter) first. It consumes
that handle and returns Some(Mutex<u64>) when it was the last strong
reference, or None otherwise. Outstanding weak references do not prevent
this extraction.
In this starter, each worker owns one clone and drops it before finishing. After every worker is joined, only the original strong handle remains. Joining arbitrary worker code would not establish that fact if it stored extra clones elsewhere.
Once you own the mutex, call Mutex::into_inner on it and handle its poison
result. This consumes the mutex without acquiring its lock. The two steps
answer different questions: who owns the mutex, and whether its protected
data has been marked poisoned.
Exclusive borrowing can also avoid locking
If a function already receives &mut Mutex<u64>, it can access the value
through get_mut:
use std::sync::Mutex;
fn bump(counter: &mut Mutex<u64>, by: u64) {
*counter.get_mut().unwrap() += by;
}
let mut counter = Mutex::new(4);
bump(&mut counter, 3);
assert_eq!(counter.into_inner().unwrap(), 7);
get_mut uses the exclusive borrow rather than acquiring the lock, while
still reporting poison. Clippy's mut_mutex_lock flags unnecessary locking
through this kind of parameter. This does not imply that guards and mutable
references have identical behavior or that every lock operation incurs a
syscall.
Avoid locking the same mutex again on the same thread while holding its guard. The standard mutex is not reentrant; that second call will not return normally and may panic or deadlock. Keep lock lifetimes visible and avoid calls that unexpectedly acquire a lock already held by the caller.
Submissions must compile, pass the cases and pass the Rust Clippy gate.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Rust reference solution
Sign in to track your attempts toward unlocking the reference solution.