BudiBadu Logo
Samplebadu

Rust by Example: Atomic Types

Rust 1.75+

Achieve high-performance concurrency with Atomic types. Learn how to use lock-free primitives like AtomicUsize and AtomicBool to share simple data across threads without the overhead of mutexes.

Code

use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::thread;

fn main() {
    // AtomicUsize is a thread-safe unsigned integer
    // It can be shared directly via Arc without a Mutex
    let val = Arc::new(AtomicUsize::new(0));

    let mut handles = vec![];

    for _ in 0..10 {
        let val = Arc::clone(&val);

        let handle = thread::spawn(move || {
            // fetch_add atomically adds to the value
            // Ordering::SeqCst provides the strongest memory guarantees
            let prev = val.fetch_add(1, Ordering::SeqCst);
            println!("Thread incremented. Previous value: {}", prev);
        });
        
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    // Load the final value
    println!("Result: {}", val.load(Ordering::SeqCst));
}

Explanation

For simple types like integers and booleans, using a Mutex can be overkill. Atomic types (like AtomicUsize, AtomicBool, etc.) provide a way to share data across threads without the overhead of locking. They rely on hardware-level atomic instructions (like Compare-And-Swap) to ensure that operations are indivisible.

Operations on atomic types take an Ordering argument. This tells the compiler and CPU how strictly they must order memory operations. Ordering::SeqCst (Sequential Consistency) is the strictest and safest default, guaranteeing that all threads see operations in the same global order. Weaker orderings like Relaxed are faster but much harder to use correctly, as they allow effects to be seen out of order.

Atomics are the building blocks for other concurrency primitives. In fact, Arc itself uses an atomic counter internally to manage its reference count! They are ideal for counters, flags, and lock-free data structures.

Code Breakdown

8
AtomicUsize::new(0). Creates a thread-safe integer. Unlike Mutex, we don't need to "lock" it to use it. It uses interior mutability to allow modification through a shared reference.
8
Arc::new(...). We still need Arc to share ownership of the atomic variable across threads, but we don't need a Mutex inside it.
18
fetch_add(1, ...). This instruction atomically reads the value, adds 1 to it, and writes it back. No other thread can interrupt this process. It returns the previous value.
30
val.load(...). Reads the current value of the atomic variable. We use load instead of simply accessing the variable because atomic operations must be explicit about memory ordering.