std::atomic
std::atomic
is a template class in the C++ Standard Library that provides a way to perform atomic operations on variables. Atomic operations are those that are performed without the possibility of interruption by other threads, making them essential for writing safe and efficient multi-threaded programs.
std::atomic
variables are guaranteed to be atomic, meaning that they are indivisible and free from race conditions. This ensures that multiple threads can operate on the same variable without causing data corruption.std::atomic
operations can be combined with memory order constraints (memory_order) to control the visibility of changes to other threads, ensuring proper synchronization.std::atomic
ensures that read, write, and modification operations are thread-safe, removing the need for explicit locking mechanisms like std::mutex
in many cases.Here's a simple example demonstrating the use of std::atomic
:
#include <iostream>
#include <atomic>
#include <thread>
std::atomic<int> counter(0); // Atomic integer
void increment() {
for (int i = 0; i < 1000; ++i) {
++counter; // Atomic increment
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << "Final counter value: " << counter << std::endl; // Should be 2000
return 0;
}
std::atomic<int>
counter(0); declares an atomic integer initialized to 0.t1
and t2
) run the increment function concurrently.
Because counter is atomic, increments are safe from race conditions.std::atomic
supports various atomic operations, including:#include <iostream>
#include <atomic>
#include <thread>
std::atomic<int> counter(0);
void increment_if_zero() {
int expected = 0;
if (counter.compare_exchange_strong(expected, 100)) {
std::cout << "Counter was zero, updated to 100" << std::endl;
} else {
std::cout << "Counter was not zero, value is " << counter.load() << std::endl;
}
}
int main() {
std::thread t1(increment_if_zero);
std::thread t2(increment_if_zero);
t1.join();
t2.join();
std::cout << "Final counter value: " << counter << std::endl;
return 0;
}
counter.compare_exchange_strong(expected, 100);
checks if counter is equal to expected (initially 0). If true, counter is set to 100, and the function returns true
. If false, expected is updated with the current value of counter, and the function returns false.std::atomic
std::atomic
operations can be combined with memory ordering options to ensure proper synchronization between threads.#include <iostream>
#include <atomic>
#include <thread>
std::atomic<int> data(0);
std::atomic<bool> ready(false);
void producer() {
data.store(42, std::memory_order_relaxed);
ready.store(true, std::memory_order_release); // Release: Ensures data is visible
}
void consumer() {
while (!ready.load(std::memory_order_acquire)); // Acquire: Waits for release
std::cout << "Data: " << data.load(std::memory_order_relaxed) << std::endl;
}
int main() {
std::thread t1(producer);
std::thread t2(consumer);
t1.join();
t2.join();
return 0;
}
data.store(42, std::memory_order_relaxed);
stores 42 without enforcing any memory ordering.ready.store(true, std::memory_order_release);
ensures that the store to data is visible to any thread that performs a subsequent acquire on ready.while (!ready.load(std::memory_order_acquire));
waits for the ready flag to be set by the producer. The acquire operation ensures that the consumer sees the updated data.std::atomic
provides atomic operations, which are essential in multi-threaded programming for ensuring data consistency and avoiding race conditions.Using std::atomic
allows you to write efficient and correct multi-threaded programs by avoiding the complexities of manual synchronization with mutexes.