Concurrency Primitives
Rule
Choose by ownership shape: owned values first, channels for transfer, standard locks for short synchronous sections, and Tokio primitives for async coordination.
Why
The smallest primitive matching data ownership is easier to reason about and less likely to block the runtime accidentally.
Activation
Apply this page for channels, locks, atomics, worker pools, shared state, or runtime coordination.
Do
- Prefer one clear owner for mutable state.
- Use bounded channels when values or commands move to an owner.
- Use
Arc<T>for cross-thread or cross-task shared ownership. - Use standard
MutexorRwLockfor short synchronous critical sections. - Use Tokio locks, semaphores, notifications, or channels when waiting is async.
- Copy or clone needed data out before
.await. - Start with
Mutex; adoptRwLockonly for meaningful read contention. - Use atomics only for simple flags, counters, or low-level coordination with obvious ordering.
- Document ordering when code can hold multiple locks.
- Follow blocking-work policy for pools and work isolation.
Avoid
- Do not choose a Tokio mutex solely because the caller is async.
- Do not hold a standard lock guard across
.await. - Do not use
Arc<Mutex<T>>to avoid selecting an owner. - Do not use channels for simple shared counters or snapshots.
- Do not use unbounded channels without an explicit memory-growth argument.
- Do not default to
RwLockor OS threads.
Async Notes
Blocking-API lints are project guardrails, not a blanket ban on std::sync; see the new-project workflow.
Example
#![allow(unused)]
fn main() {
pub async fn handle_job(cache: &Mutex<Cache>, client: &Client, key: Key) -> Result<(), Error> {
let path = {
let mut cache = cache.lock().expect("cache mutex should not be poisoned");
cache.entry(key).or_insert_with(default_path).clone()
};
let bytes = read_file(path).await?;
client.upload(bytes).await
}
}
Exceptions
- Use an async lock when waiting must be async or a guard intentionally spans
.await. - Use an unbounded channel for a naturally bounded stream or documented best-effort telemetry.
- Use an OS thread for affinity or long-lived blocking ownership, with a lint reason.