Implementing Spinlocks with C++11 atomic_flag

Understanding atomic_flag

The atomic_flag is the simplest atomic boolean type in C++11's <atomic> header, supporting only two operations: test_and_set and clear.

Constructor and Initialization

atomic_flag() noexcept = default;
atomic_flag(const atomic_flag&) = delete;

atomic_flag has only a default constructor. Copy construction is disabled, and objects must be initialized using ATOMIC_FLAG_INIT to guarantee a clear initial state. Without explicit initialization, the sttate is unspecified.

Basic Usage Example

#include <atomic>
#include <thread>
#include <vector>

std::atomic<bool> startSignal(false);
std::atomic_flag firstCompleted = ATOMIC_FLAG_INIT;

void computeTask(int id) {
    while (!startSignal) {}
    
    for (int i = 0; i < 1000000; ++i) {}
    
    if (!firstCompleted.test_and_set()) {
        std::cout << "Thread " << id << " finished first\n";
    }
}

int main() {
    std::vector<std::thread> workers;
    for (int i = 0; i < 10; ++i) {
        workers.emplace_back(computeTask, i);
    }
    startSignal = true;
    
    for (auto& worker : workers) {
        worker.join();
    }
}

test_and_set Operation

The test_and_set function atomically checks and sets the flag:

bool test_and_set(memory_order order = memory_order_seq_cst) noexcept;

It returns the previous state and sets the flag to true. Memory order options include:

Memory Order Type
memory_order_relaxed Relaxed
memory_order_consume Consume
memory_order_acquire Acquire
memory_order_release Release
memory_order_acq_rel Acquire/Release
memory_order_seq_cst Sequentially consistent

clear Operation

The clear function resets the flag to false:

void clear(memory_order order = memory_order_seq_cst) noexcept;

This enables subsequent test_and_set calls to return false.

Spinlock Implementation

#include <atomic>
#include <thread>

std::atomic_flag lockFlag = ATOMIC_FLAG_INIT;

void criticalSection(int id) {
    for (int i = 0; i < 100; ++i) {
        while (lockFlag.test_and_set(std::memory_order_acquire)) {}
        std::cout << "Thread " << id << " entered\n";
        lockFlag.clear(std::memory_order_release);
    }
}

int main() {
    std::thread t1(criticalSection, 1);
    std::thread t2(criticalSection, 2);
    t1.join();
    t2.join();
}

This pattern implements a spinlock where threads wait in a loop (test_and_set) untill they can atomically acquire the lock, then releace it with clear.

Tags: C++11 atomic_flag spinlock Concurrency memory_order

Posted on Wed, 09 Sep 2026 16:07:04 +0000 by RestlessThoughts