Locking and Unlocking
In Rust, locking a Mutex<T> is an explicit method call (.lock() or .try_lock()), whereas unlocking is performed automatically via RAII when the returned MutexGuard<'_, T> is dropped (either at the end of its lexical block or via an explicit drop(guard) call). There is no mutex.unlock() method on Mutex<T> itself.
Why Mutex<T> Has No .unlock() Method
If Mutex<T> had a .unlock() method while a MutexGuard<'_, T> or a reference &mut T derived from the guard was still in scope, calling mutex.unlock() would release the lock while the caller continued mutating &mut T concurrently with the next thread that locked the mutex!
By tying the lock state strictly to the lifetime 'a of MutexGuard<'a, T>:
- Any reference
&'b mut Tborrowed from the guard cannot outlive the guard ('a: 'b). - Dropping the guard (
drop(guard)) requires that no active&mut Tborrow derived fromguardis still in use!
Lifetime Enforcement of `MutexGuard<'a, T>`:
let mut guard: MutexGuard<'_, Vec<i32>> = mutex.lock().unwrap();
let elem_ref: &mut i32 = &mut guard[0];
drop(guard); // <-- Compile Error E0505 if `elem_ref` is used after this line!
*elem_ref = 99; // (Prevents mutating inner data after the lock is released!)
Non-Blocking Acquisition with .try_lock()
While .lock() blocks the calling thread indefinitely until the mutex becomes available, .try_lock() attempts to acquire the mutex immediately and returns a TryLockResult<MutexGuard<'_, T>> (Result<MutexGuard<'_, T>, TryLockError<MutexGuard<'_, T>>>):
try_lock() Return Variant |
Meaning |
|---|---|
Ok(guard) |
Lock was free and is now held by guard |
Err(TryLockError::WouldBlock) |
Another thread (or an earlier guard on the same thread) currently holds the lock |
Err(TryLockError::Poisoned(p)) |
Lock was free, but a previous holder panicked while holding it |
use std::sync::{Mutex, TryLockError};
use std::thread;
fn main() {
let resource = Mutex::new(String::from("idle"));
// Hold the lock on the main thread:
let mut main_guard = resource.lock().unwrap();
*main_guard = String::from("busy-on-main");
thread::scope(|s| {
s.spawn(|| {
// Non-blocking check from a worker thread while `main_guard` is active:
match resource.try_lock() {
Ok(guard) => println!("worker acquired lock: {}", *guard),
Err(TryLockError::WouldBlock) => {
println!("worker saw WouldBlock; skipping contended work")
}
Err(TryLockError::Poisoned(_)) => println!("lock poisoned"),
}
});
});
// Explicitly unlock on main thread before acquiring again:
drop(main_guard);
println!("after drop(main_guard), try_lock succeeds: {:?}", resource.try_lock().unwrap());
}
worker saw WouldBlock; skipping contended work
after drop(main_guard), try_lock succeeds: "busy-on-main"
Zero-Lock Exclusive Access with Mutex::get_mut and Mutex::into_inner
When a single thread holds an exclusive mutable reference &mut Mutex<T> (for example, after all worker threads in thread::scope have finished or after Arc::try_unwrap succeeds), calling .get_mut() or .into_inner() accesses the inner T with zero atomic instructions because the type system guarantees no other thread has a reference to the mutex:
use std::sync::Mutex;
use std::thread;
fn main() {
let mut accumulator = Mutex::new(Vec::new());
thread::scope(|s| {
for i in 1..=3 {
let acc_ref = &accumulator;
s.spawn(move || {
acc_ref.lock().unwrap().push(i * 10);
});
}
});
// Now that the scope has ended, `main` has exclusive `&mut accumulator` again.
// `.get_mut()` sorts in place without locking!
accumulator.get_mut().unwrap().sort();
// `.into_inner()` unwraps the `Vec<i32>` out of the `Mutex` by value:
let final_vec: Vec<i32> = accumulator.into_inner().unwrap();
println!("final_vec: {:?}", final_vec);
}
final_vec: [10, 20, 30]