<atomic>
The <atomic>
header in C++ provides a standardized way to perform atomic operations on data types. Atomic operations are indivisible, meaning they appear to occur instantaneously to other threads. This header is crucial for writing thread-safe code and implementing lock-free algorithms in concurrent programming.
std::atomic<int>
#include <atomic>
#include <thread>
#include <iostream>
std::atomic<int> counter(0);
void increment_counter() {
for (int i = 0; i < 1000; ++i) {
counter++;
}
}
int main() {
std::thread t1(increment_counter);
std::thread t2(increment_counter);
t1.join();
t2.join();
std::cout << "Final counter value: " << counter << std::endl;
return 0;
}
std::atomic<int>
variable called counter
.std::atomic_flag
for Simple Synchronization#include <atomic>
#include <thread>
#include <iostream>
#include <vector>
std::atomic_flag lock = ATOMIC_FLAG_INIT;
int shared_resource = 0;
void increment_resource() {
for (int i = 0; i < 100; ++i) {
while (lock.test_and_set(std::memory_order_acquire)) { } // Spin lock
shared_resource++;
lock.clear(std::memory_order_release);
}
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 10; ++i) {
threads.emplace_back(increment_resource);
}
for (auto& t : threads) {
t.join();
}
std::cout << "Final shared_resource value: " << shared_resource << std::endl;
return 0;
}
std::atomic_flag
as a simple spin lock.test_and_set()
atomically sets the flag and returns its previous value.clear()
.shared_resource
at a time.std::atomic<bool>
#include <atomic>
#include <thread>
#include <iostream>
std::atomic<bool> x(false);
std::atomic<bool> y(false);
std::atomic<int> z(0);
void write_x_then_y() {
x.store(true, std::memory_order_relaxed);
y.store(true, std::memory_order_release);
}
void read_y_then_x() {
while (!y.load(std::memory_order_acquire));
if (x.load(std::memory_order_relaxed)) {
++z;
}
}
int main() {
std::thread a(write_x_then_y);
std::thread b(read_y_then_x);
a.join();
b.join();
std::cout << "z = " << z << std::endl;
return 0;
}
std::atomic<bool>
.memory_order_relaxed
is used for operations without synchronization requirements.memory_order_release
ensures all previous writes are visible to a thread doing an acquire operation.memory_order_acquire
ensures subsequent reads see the released operation.y
is observed as true, x
must also be true.std::atomic
#include <atomic>
#include <memory>
#include <iostream>
template<typename T>
class lock_free_stack {
struct node {
T data;
node* next;
node(const T& data) : data(data), next(nullptr) {}
};
std::atomic<node*> head;
public:
lock_free_stack() : head(nullptr) {}
void push(const T& data) {
node* new_node = new node(data);
new_node->next = head.load(std::memory_order_relaxed);
while (!head.compare_exchange_weak(new_node->next, new_node,
std::memory_order_release,
std::memory_order_relaxed));
}
bool pop(T& result) {
node* old_head = head.load(std::memory_order_relaxed);
while (old_head && !head.compare_exchange_weak(old_head, old_head->next,
std::memory_order_acquire,
std::memory_order_relaxed));
if (old_head) {
result = old_head->data;
delete old_head;
return true;
}
return false;
}
};
int main() {
lock_free_stack<int> stack;
stack.push(1);
stack.push(2);
stack.push(3);
int value;
while (stack.pop(value)) {
std::cout << "Popped: " << value << std::endl;
}
return 0;
}
std::atomic<node*>
.push()
uses compare_exchange_weak()
to atomically update the head pointer.pop()
also uses compare_exchange_weak()
to atomically remove the top node.memory_order_release
in push ensures the new node is fully constructed before it's visible.memory_order_acquire
in pop ensures the popped node's data is visible to the current thread.Performance: Atomic operations can be more expensive than non-atomic operations, but they're often faster than using mutexes for simple operations.
ABA Problem: In lock-free algorithms, be aware of the ABA problem where a value changes from A to B and back to A, potentially causing issues in compare-and-swap operations.
Memory Management: When using atomic pointers in lock-free data structures, proper memory management (like hazard pointers or reference counting) is crucial to avoid memory leaks and use-after-free bugs.
Portability: While <atomic>
is part of the C++ standard, the actual implementation of atomic operations may vary across different architectures.
The <atomic>
header in C++ provides essential tools for concurrent programming:
std::atomic<T>
can be used with various types, including integers, booleans, and pointers.std::atomic_flag
provides a simple, lock-free synchronization mechanism.By using atomic operations, developers can write more efficient and correct concurrent code, avoiding common pitfalls like race conditions and deadlocks. However, it's important to use these tools judiciously and understand their implications on performance and system behavior.