До C++11 стандарт не формализовал многопоточное поведение, что приводило к проблемам переносимости. C++11 ввел строгую модель памяти, которая:
#include <atomic>
std::atomic<int> counter(0); // Гарантированно атомарный
counter.fetch_add(1, std::memory_order_relaxed);
Особенности:
atomic
не могут быть "разорваны"load()
, store()
, exchange()
, compare_exchange_weak/strong
std::atomic<bool> ready(false);
int data = 0;
// Поток 1:
data = 42; // (1)
ready.store(true, std::memory_order_release); // (2)
// Поток 2:
while(!ready.load(std::memory_order_acquire)); // (3)
std::cout << data; // (4)
Основные порядки:
memory_order_seq_cst
(по умолчанию) - строгий порядокmemory_order_acquire
- гарантирует, что последующие операции не будут переупорядочены до этойmemory_order_release
- гарантирует, что предыдущие операции не будут переупорядочены после этойmemory_order_relaxed
- нет гарантий порядка, только атомарностьstd::atomic_thread_fence(std::memory_order_release);
// Операции до барьера не будут перемещены после
std::atomic_thread_fence(std::memory_order_acquire);
// Операции после барьера не будут перемещены до
Паттерн "Double-Checked Locking":
class Singleton {
static std::atomic<Singleton*> instance;
static std::mutex mtx;
public:
static Singleton* get() {
Singleton* tmp = instance.load(std::memory_order_acquire);
if (tmp == nullptr) {
std::lock_guard<std::mutex> lock(mtx);
tmp = instance.load(std::memory_order_relaxed);
if (tmp == nullptr) {
tmp = new Singleton();
instance.store(tmp, std::memory_order_release);
}
}
return tmp;
}
};
C++11 модель памяти:
Для большинства случаев достаточно memory_order_seq_cst
, но в высоконагруженных системах оптимизация с более слабыми порядками может дать выигрыш в производительности.