---
title: Threads and Synchronization Primitives
url: https://doc.liz6.com/en/rust/06-concurrency-and-asynchrony/04-threads-and-synchronization-primitives
locale: en
area: rust
tags:
- rust
- concurrency-and-asynchrony
date: 2026-06-30
modified: 2026-07-16
description: The concurrency toolbox of the Rust standard library—Mutex/RwLock provide shared mutable access, mpsc channels pass messages between threads (without sharing memory), Condvar handles condition waiting, and atomics and Ordering form the foundation of lock-free concurrency. Each tool corresponds to a pattern of "passing information between threads."
---

# Threads and Synchronization Primitives

> The concurrency toolbox of the Rust standard library—Mutex/RwLock provide shared mutable access, mpsc channels pass messages between threads (without sharing memory), Condvar handles condition waiting, and atomics and Ordering form the foundation of lock-free concurrency. Each tool corresponds to a pattern of "passing information between threads."

## std::thread

```rust
let handle = std::thread::spawn(|| {
    println!("hello from another thread");
});
handle.join().unwrap();              // Wait for the thread to finish
```

Each thread has its own stack (2MB by default on Linux, adjustable via `Builder::stack_size`). The closure passed to `std::thread::spawn` must be `Send + 'static`—the thread may outlive the spawner.

## Mutex<T>: Mutex Lock

```rust
use std::sync::Mutex;
let m = Mutex::new(0);
* m.lock().unwrap() += 1;            // lock() returns a MutexGuard
// MutexGuard drop → automatically unlocks (even in case of panic, unwinding will drop)
```

`MutexGuard<'_, T>` implements `DerefMut` (giving you `&mut T`) and `Drop` (releasing the lock). There is no need to manually call `unlock()`. If the thread holding the lock panics, the Mutex becomes "poisoned"—subsequent calls to `lock()` return `Err(PoisonError)`, alerting you that the data might be in an inconsistent state.

## RwLock<T>: Read-Write Lock

```rust
use std::sync::RwLock;
let lock = RwLock::new(42);
let r1 = lock.read().unwrap();       // Shared read lock
let r2 = lock.read().unwrap();       // Multiple readers can coexist
// let w = lock.write().unwrap();    // WOULD BLOCK: readers are active
drop(r1); drop(r2);                  // readers gone
let mut w = lock.write().unwrap();   // Now writing is allowed
```

RwLock is suitable for scenarios with many reads and few writes. However, be aware: if readers are continuous, writers may starve. `std::sync::RwLock` does not guarantee fairness (it is not FIFO)—specific behavior depends on the OS scheduler.

## mpsc: Multi-Producer Single-Consumer Channel

```rust
use std::sync::mpsc;
let (tx, rx) = mpsc::channel();
std::thread::spawn(move || { tx.send("hello").unwrap(); });
let msg = rx.recv().unwrap();        // Block waiting for a message
```

`recv()` blocks the current thread until a message arrives—this is not async. For async scenarios, use `tokio::sync::mpsc`. When all `tx` senders are dropped, `rx.recv()` returns `Err(RecvError)`—this is important: the "closing" of a channel is implicitly communicated via the Drop of the sender.

## Condvar: Condition Variable

```rust
use std::sync::{Arc, Mutex, Condvar};
let pair = Arc::new((Mutex::new(false), Condvar::new()));

// Waiting thread:
let (lock, cvar) = &*pair;
let mut guard = lock.lock().unwrap();
while !*guard {
    guard = cvar.wait(guard).unwrap();  // Atomic operation: release lock + block
}

// Notifying thread:
let (lock, cvar) = &*pair;
*lock.lock().unwrap() = true;
cvar.notify_one();
```

You must use a `while` loop instead of an `if` statement to check the condition—spurious wakeups can occur (the OS might wake up the thread even if the condition is not met).

## Atomic and Ordering

```rust
use std::sync::atomic::{AtomicUsize, Ordering};
static COUNTER: AtomicUsize = AtomicUsize::new(0);
COUNTER.fetch_add(1, Ordering::SeqCst);
```

**Relaxed**: Only guarantees atomicity, not the order relative to surrounding memory operations—Use case: simple counting, no need to synchronize with other variables.
**Acquire/Release**: One-way synchronization—Acquire ensures that subsequent reads see writes that happened before Release. Use case: implementing locks.
**SeqCst**: Strongest—globally consistent ordering. Use case: default choice, unless you know why you need a weaker ordering.

## References

- **Rust Book**: Chapter 16
- **Rustonomicon**: Atomics, memory ordering
- **CppReference**: memory_order (the definition in C++ also applies to Rust's atomic model)

*Keywords: thread, Mutex, RwLock, mpsc, channel, Condvar, AtomicUsize, Ordering, SeqCst, Acquire, Release*
