Course contentsShow
← Rust
Lesson 1280 of 1,91011. Asynchronous ProgrammingFree lesson

Atomic Operations in Async Logic

Use atomic primitives for lock-free coordination between multiple polling tasks.

In this lesson, you will learn how to use atomic primitives to achieve lock-free coordination between multiple asynchronous tasks without the overhead of an Async Mutex.

The Problem with Async Mutexes

In high-concurrency async systems, frequent context switching caused by Mutex contention can degrade performance. When you only need to manage a simple flag or a counter shared across tasks, an Async Mutex is often overkill. Using std::sync::atomic types allows you to perform these updates in constant time without ever suspending the task or interacting with the task executor’s scheduler.

Lock-Free Coordination

Because atomic operations are CPU-level primitives, they are non-blocking. When a task updates an atomic value, it doesn't need to yield control back to the executor. This is ideal for scenarios like tracking global task progress or signaling state changes (e.g., "should I stop working?").

Consider a scenario where multiple background tasks need to know if a shared shutdown signal has been triggered:

use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;

struct Coordinator {
    is_shutdown: AtomicBool,
}

async fn worker_task(coord: Arc<Coordinator>) {
    // Check the atomic flag without holding a lock or awaiting
    while !coord.is_shutdown.load(Ordering::Relaxed) {
        // Do asynchronous work...
        tokio::task::yield_now().await;
    }
}

Choosing the Right Ordering

The Ordering parameter determines how memory visibility is synchronized across threads. While Ordering::Relaxed is the fastest, it provides the fewest guarantees. If you are coordinating complex state transitions, you might need Ordering::Acquire and Ordering::Release to ensure that memory changes made before a signal are visible to the task that reads it.

By keeping state modifications atomic, you prevent "deadlock" scenarios where tasks wait on each other’s locks. Since atomic operations never put a task into a Pending state, your reactor remains focused on I/O events rather than managing complex lock queues.

Key Takeaway: Use std::sync::atomic types for simple shared flags and counters to enable lock-free state synchronization that keeps your tasks non-blocking and highly performant.