Theme
Q047. How do std::sync locks differ from tokio::sync locks?
Short Answer
std::sync locks block the current OS thread when contended. Tokio locks return futures, suspend the waiting task, and let the executor run other work; their guards are designed to survive .await. Tokio locks are therefore appropriate when a critical section must span asynchronous operations. For short protection of ordinary data with no .await, a standard mutex is often cheaper even inside async code.
Deep Dive
The decision is about what happens while waiting and whether the guard crosses a suspension point, not simply whether the surrounding function is async.
Use std::sync::Mutex when contention is low, the critical section is short, and the guard is always released before .await. Use tokio::sync::Mutex when lock acquisition must be asynchronous or the protected resource must remain locked across an asynchronous operation. Consider an actor task and channel when many operations naturally belong to one resource owner.
The semantics also differ. Standard Mutex and RwLock support poisoning after a panic while holding a write-capable guard. Tokio locks do not poison. Tokio's mutex uses a FIFO acquisition queue, and its RwLock documents a fair, write-preferring policy.
Internal Model
A contended standard lock can park the worker thread in the OS. A contended Tokio lock registers task wake-up state and returns Pending, allowing the worker to poll another task.
Async-aware bookkeeping and wake queues cost more than the fast path of a standard lock. Neither choice makes a long critical section harmless.
Example
rust
use std::sync::{Arc, Mutex};
#[tokio::main]
async fn main() {
let counter = Arc::new(Mutex::new(0_u64));
{
let mut guard = counter.lock().unwrap();
*guard += 1;
} // The standard guard is released before the await.
tokio::task::yield_now().await;
assert_eq!(*counter.lock().unwrap(), 1);
}Common Mistakes
- Replacing every standard mutex with a Tokio mutex inside async code.
- Holding a standard lock guard across
.await. - Assuming an async mutex prevents blocking work inside the critical section.
Follow-ups
- Why can a standard mutex be acceptable in an async service?
- What is lock poisoning, and why does Tokio not use it?
- When is an actor preferable to an async mutex?
Related
- Q046. When should you use a
Mutexversus anRwLock? - Q048. Why is holding a lock guard across
.awaitdangerous? - Q071. Why is blocking code dangerous inside async tasks?
References
- Rust Standard Library
- Tokio Documentation