Atomic Counters in Rust

Our primary mechanism for managing state in Rust is through ownership and borrowing. However, there are situations where we need to share state across multiple threads. In this example, we’ll look at using atomic operations for concurrent counters accessed by multiple threads.

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

fn main() {
    // We'll use an atomic integer type to represent our
    // (always-positive) counter.
    let ops = Arc::new(AtomicU64::new(0));

    // We'll start 50 threads that each increment the
    // counter exactly 1000 times.
    let mut handles = vec![];

    for _ in 0..50 {
        let ops_clone = Arc::clone(&ops);
        let handle = thread::spawn(move || {
            for _ in 0..1000 {
                // To atomically increment the counter we use fetch_add.
                ops_clone.fetch_add(1, Ordering::SeqCst);
            }
        });
        handles.push(handle);
    }

    // Wait until all the threads are done.
    for handle in handles {
        handle.join().unwrap();
    }

    // Here no threads are writing to 'ops', but using
    // load it's safe to atomically read a value even while
    // other threads might be (atomically) updating it.
    println!("ops: {}", ops.load(Ordering::SeqCst));
}

To run the program:

$ cargo run
ops: 50000

We expect to get exactly 50,000 operations. Had we used a non-atomic integer and incremented it with regular addition, we’d likely get a different number, changing between runs, because the threads would interfere with each other. Moreover, Rust’s ownership system would prevent us from sharing a non-atomic value between threads without proper synchronization.

In Rust, we use Arc (Atomic Reference Counting) to share the atomic counter across multiple threads. The AtomicU64 type provides atomic operations, and we use fetch_add for atomic increments. The Ordering::SeqCst parameter specifies the memory ordering guarantee for the atomic operations.

Next, we’ll look at mutexes, another tool for managing state across threads.