Course contentsShow
← Rust
Lesson 449 of 1,9104. Lifetimes and Advanced OwnershipFree lesson

Reducing Atomic Contention

Replace global atomics with thread-local storage for better performance in concurrent loops.

In this lesson, you will learn how to boost concurrent performance by replacing shared global atomics with thread-local storage to eliminate synchronization bottlenecks.

The Cost of Synchronization

When you use a global AtomicU64 to track a counter across multiple threads, you create a "hot spot." Every time a thread performs an operation, it must communicate with the CPU's cache-coherency protocol to gain exclusive access to that memory address. If you have dozens of threads incrementing a counter inside a tight loop, they spend more time fighting for control of that cache line than they do performing actual work. This is called atomic contention.

Solving Contention with Thread-Local Storage

Instead of forcing every thread to fight for one global variable, you can give each thread its own private "scratchpad" using the thread_local! macro. Threads perform their updates locally without any cross-thread communication. Only when the thread finishes its work (or when you explicitly need the aggregate value) do you merge the local results back into the global state.

Consider this pattern:

use std::cell::Cell;

thread_local! {
    static LOCAL_COUNT: Cell<u64> = Cell::new(0);
}

fn increment_local() {
    LOCAL_COUNT.with(|count| {
        count.set(count.get() + 1);
    });
}

By using thread_local!, we remove the need for AtomicU64 entirely within the inner loop. Each core updates its own cached memory independently. Because the memory is private to the thread, the CPU doesn't need to perform expensive cache-invalidation cycles, allowing the loop to run at full speed.

When to Merge

You might wonder: "If the data is local, how do I get the total?" You only pay the synchronization tax once at the end of the operation. By keeping the "contended" part of your code (the merging step) outside of the high-frequency loop, you effectively trade constant, small-scale contention for a single, low-impact synchronization point. This transforms a performance-draining bottleneck into a highly scalable parallel pipeline.

Key Takeaway: Replace global atomics with thread-local storage to minimize cache-coherency traffic and keep your high-frequency loops free from synchronization overhead.