| name | rust-concurrency |
| description | Low-level concurrency in Rust including atomics, memory ordering, building locks/channels, Send/Sync, and thread synchronization. Use when implementing lock-free data structures, understanding memory ordering, building synchronization primitives, or debugging race conditions. |
Rust Concurrency & Atomics
Comprehensive guide based on Rust Atomics and Locks by Mara Bos and The Rust Programming Language Ch. 16.
When to Use This Skill
- Implementing lock-free or wait-free data structures
- Understanding
Ordering (Relaxed, Acquire, Release, AcqRel, SeqCst)
- Building custom mutexes, channels, or condition variables
- Debugging data races or incorrect synchronization
- Understanding
Send, Sync, and thread safety markers
- Using
Arc<Mutex<T>> and Arc<RwLock<T>> patterns
- Working with
std::sync::atomic types
Thread Safety Markers
Send
A type is Send if it can be transferred to another thread. Most types are Send. Notable exceptions: Rc<T>, raw pointers, MutexGuard (on some platforms).
Sync
A type is Sync if &T is Send (safe to share references across threads). Examples: Mutex<T> is Sync (even though T isn't necessarily), Cell<T> is NOT Sync.
unsafe impl Send for MyWrapper {}
unsafe impl Sync for MyWrapper {}
Atomic Operations
Types
AtomicBool, AtomicI8..AtomicI64, AtomicU8..AtomicU64, AtomicUsize, AtomicPtr<T>
Core Operations
use std::sync::atomic::{AtomicU64, Ordering};
let counter = AtomicU64::new(0);
counter.store(42, Ordering::Release);
let val = counter.load(Ordering::Acquire);
let old = counter.swap(100, Ordering::AcqRel);
counter.fetch_add(1, Ordering::Relaxed);
counter.fetch_sub(1, Ordering::Relaxed);
counter.fetch_or(mask, Ordering::Relaxed);
counter.fetch_and(!mask, Ordering::Relaxed);
let result = counter.compare_exchange(
expected,
new_value,
Ordering::AcqRel,
Ordering::Acquire,
);
Memory Ordering
| Ordering | Guarantees |
|---|
Relaxed | Atomicity only; no ordering with other operations |
Acquire | Loads: all subsequent reads/writes see effects before the paired Release |
Release | Stores: all prior reads/writes are visible to the paired Acquire |
AcqRel | Both Acquire and Release (for read-modify-write ops) |
SeqCst | Total global ordering; all threads see the same order |
Acquire-Release Pattern
DATA.store(value, Ordering::Relaxed);
READY.store(true, Ordering::Release);
while !READY.load(Ordering::Acquire) {}
let val = DATA.load(Ordering::Relaxed);
When to Use What
- Relaxed: Simple counters, statistics, progress indicators
- Acquire/Release: Publish data between threads (producer-consumer)
- SeqCst: When you need total ordering (rare; usually overkill)
Building a SpinLock
use std::sync::atomic::{AtomicBool, Ordering};
use std::cell::UnsafeCell;
pub struct SpinLock<T> {
locked: AtomicBool,
value: UnsafeCell<T>,
}
unsafe impl<T: Send> Sync for SpinLock<T> {}
impl<T> SpinLock<T> {
pub fn lock(&self) -> &mut T {
while self.locked.swap(true, Ordering::Acquire) {
std::hint::spin_loop();
}
unsafe { &mut *self.value.get() }
}
pub fn unlock(&self) {
self.locked.store(false, Ordering::Release);
}
}
Standard Library Primitives
Mutex
use std::sync::{Arc, Mutex};
let data = Arc::new(Mutex::new(vec![]));
let data_clone = Arc::clone(&data);
std::thread::spawn(move || {
let mut lock = data_clone.lock().unwrap();
lock.push(42);
});
RwLock
use std::sync::RwLock;
let lock = RwLock::new(HashMap::new());
let r = lock.read().unwrap();
let mut w = lock.write().unwrap();
Channels (mpsc)
use std::sync::mpsc;
let (tx, rx) = mpsc::channel();
let tx2 = tx.clone();
std::thread::spawn(move || tx.send("hello").unwrap());
std::thread::spawn(move || tx2.send("world").unwrap());
for msg in rx { println!("{msg}"); }
Reference Map
references/atomics-ordering.md — atomic types, operations, memory ordering in depth
references/building-primitives.md — implementing Mutex, Channel, Condvar from atomics
references/thread-patterns.md — Arc+Mutex, scoped threads, thread pools, parking
Key References