CodeNebula
C++20stladvanced

std::atomic_flag Enhancements (C++20 Evolution)

C++20 · The Big Four · Compiler flag: -std=c++20

Explanation

`std::atomic_flag` gains `test()` (read without modify), `wait()` (block until value changes), `notify_one()`, and `notify_all()`. These additions make `atomic_flag` a complete synchronization primitive.

Real-World Scenario

Implement a lightweight spinlock with wait/notify for efficient thread signaling without busy-waiting.

Quick Snippet

snippet.cpp
#include <atomic>
#include <thread>

std::atomic_flag ready{};

// Producer
void signal() {
    ready.test_and_set();
    ready.notify_one();
}

// Consumer
void wait_for_signal() {
    ready.wait(false);  // blocks until flag becomes true
}

Full Example

atomic_flag.cpp
// CodeNebula - C++20 Example: std::atomic_flag Enhancements (test, wait/notify)
// Compile: g++ -std=c++20 -Wall -Wextra -pthread -o example atomic_flag.cpp

#include <atomic>
#include <chrono>
#include <iostream>
#include <thread>
#include <vector>

int main() {
    std::cout << "=== std::atomic_flag C++20 enhancements ===\n\n";

    // C++20: atomic_flag is default-initialized to clear state
    std::atomic_flag flag;

    // C++20 addition: test() - read the flag without modifying it
    std::cout << "--- test() (non-modifying read) ---\n";
    std::cout << "Initial state: " << flag.test() << " (0 = clear)\n";

    flag.test_and_set();
    std::cout << "After test_and_set(): " << flag.test() << " (1 = set)\n";

    flag.clear();
    std::cout << "After clear(): " << flag.test() << " (0 = clear)\n";

    std::cout << "\n=== atomic_flag::wait/notify ===\n";
    std::cout << "(Efficient blocking until flag changes)\n\n";

    // Spinlock using atomic_flag
    std::cout << "--- Spinlock example ---\n";
    std::atomic_flag lock = ATOMIC_FLAG_INIT;
    int shared_data = 0;

    auto critical_section = [&](int id) {
        for (int i = 0; i < 1000; ++i) {
            while (lock.test_and_set(std::memory_order_acquire)) {
                // Spin (in real code, use wait() for efficiency)
            }
            ++shared_data;  // Protected by spinlock
            lock.clear(std::memory_order_release);
        }
        std::cout << "Thread " << id << " done\n";
    };

    {
        std::jthread t1(critical_section, 1);
        std::jthread t2(critical_section, 2);
    }
    std::cout << "Shared data: " << shared_data << " (expected 2000)\n";

    std::cout << "\n--- wait/notify signaling ---\n";

    std::atomic_flag ready;
    ready.clear();

    std::jthread producer([&ready] {
        std::this_thread::sleep_for(std::chrono::milliseconds(50));
        std::cout << "Producer: data is ready\n";
        ready.test_and_set();
        ready.notify_one();  // Wake up one waiting thread
    });

    std::jthread consumer([&ready] {
        std::cout << "Consumer: waiting for data...\n";
        ready.wait(false);  // Block until flag != false (i.e., until set)
        std::cout << "Consumer: got the signal, processing!\n";
    });

    producer.join();
    consumer.join();

    std::cout << "\n--- notify_all example ---\n";

    std::atomic_flag start_signal;
    start_signal.clear();

    auto racer = [&start_signal](int id) {
        start_signal.wait(false);  // Wait until flag is set
        std::cout << "Racer " << id << " started!\n";
    };

    std::vector<std::jthread> racers;
    for (int i = 0; i < 4; ++i) {
        racers.emplace_back(racer, i);
    }

    std::this_thread::sleep_for(std::chrono::milliseconds(30));
    std::cout << "GO!\n";
    start_signal.test_and_set();
    start_signal.notify_all();  // Wake ALL waiting threads

    for (auto& r : racers) r.join();

    std::cout << "\n=== Properties ===\n";
    std::cout << "is_always_lock_free: " << std::atomic_flag::is_always_lock_free << "\n";

    return 0;
}
Run command
g++ -std=c++20 -Wall -Wextra -pthread -o example atomic_flag.cpp