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.