Барьеры памяти (memory barriers/fences) — это механизмы синхронизации, которые контролируют порядок выполнения операций с памятью в многопоточных и многопроцессорных системах. Они предотвращают потенциально опасные перестановки операций процессором и компилятором.
Современные системы выполняют оптимизации, которые могут нарушать ожидаемый порядок операций:
Пример проблемы без барьера:
// Поток 1
x = 1;
ready = true;
// Поток 2
while(!ready);
std::cout << x; // Может показать 0, даже если x=1 уже записан!
Самый строгий, гарантирует порядок всех операций до и после:
std::atomic_thread_fence(std::memory_order_seq_cst);
Запрещает перестановку операций после барьера с операциями до него:
std::atomic_thread_fence(std::memory_order_acquire);
// Никакие чтения/записи после не могут быть перемещены ДО барьера
Запрещает перестановку операций до барьера с операциями после:
std::atomic_thread_fence(std::memory_order_release);
// Никакие чтения/записи до не могут быть перемещены ПОСЛЕ барьера
Комбинация acquire и release семантики:
std::atomic_thread_fence(std::memory_order_acq_rel);
// Поток 1 (писатель)
data = 42; // (1)
std::atomic_thread_fence(std::memory_order_release); // (2)
flag.store(true, std::memory_order_relaxed); // (3)
// Поток 2 (читатель)
while(!flag.load(std::memory_order_relaxed)); // (4)
std::atomic_thread_fence(std::memory_order_acquire); // (5)
std::cout << data; // (6)
Гарантируется, что если поток 2 увидел flag=true, то data=42.
Пример из реализации очереди:
void push(Node* new_node) {
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));
}
На разных архитектурах барьеры реализуются по-разному:
Атомарные операции неявно содержат барьеры:
// Эквивалентно с барьером
x.store(42, std::memory_order_release);
// Эквивалентная реализация с fence
std::atomic_thread_fence(std::memory_order_release);
x.store(42, std::memory_order_relaxed);
Барьеры предотвращают нежелательные оптимизации:
// Без барьера компилятор может переставить
x = 1;
y = 2; // Может быть выполнено до x=1
// С барьером порядок гарантирован
x = 1;
std::atomic_thread_fence(std::memory_order_release);
y = 2;
Резюмируем: барьеры памяти — мощный инструмент для управления видимостью операций с памятью между потоками. Их правильное использование требует глубокого понимания модели памяти, но критически важно для написания корректных многопоточных программ.