Расскажите о модели памяти, которая появилась в С++11 стандарте.cplus-74

Что изменилось в C++11

До C++11 стандарт не формализовал многопоточное поведение, что приводило к проблемам переносимости. C++11 ввел строгую модель памяти, которая:

  • Определяет, как потоки взаимодействуют через общую память
  • Гарантирует предсказуемое поведение атомарных операций
  • Вводит новые механизмы синхронизации

Ключевые компоненты модели

1. Атомарные операции и типы

#include <atomic>
std::atomic<int> counter(0); // Гарантированно атомарный
counter.fetch_add(1, std::memory_order_relaxed);

Особенности:

  • Операции над atomic не могут быть "разорваны"
  • Доступны специальные функции: load(), store(), exchange(), compare_exchange_weak/strong

2. Порядки памяти

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)

Основные порядки:

  1. memory_order_seq_cst (по умолчанию) - строгий порядок
  2. memory_order_acquire - гарантирует, что последующие операции не будут переупорядочены до этой
  3. memory_order_release - гарантирует, что предыдущие операции не будут переупорядочены после этой
  4. memory_order_relaxed - нет гарантий порядка, только атомарность

3. Барьеры памяти

std::atomic_thread_fence(std::memory_order_release);
// Операции до барьера не будут перемещены после
std::atomic_thread_fence(std::memory_order_acquire);
// Операции после барьера не будут перемещены до

4. Гарантии видимости изменений

  • Happens-before - если операция A happens-before B, то A будет видна для B
  • Synchronizes-with - специальное отношение между операциями в разных потоках

Практическое применение

Паттерн "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;
    }
};

Опасные моменты

  1. Data Races - несинхронизированный доступ к неатомарным данным из разных потоков - это UB
  2. False Sharing - когда разные потоки модифицируют разные переменные в одной кэш-линии
  3. Чрезмерное использование seq_cst - может снижать производительность

Резюмируем

C++11 модель памяти:

  1. Формализует многопоточное поведение
  2. Вводит атомарные типы с различными порядками доступа
  3. Позволяет писать эффективный и переносимый многопоточный код
  4. Требует аккуратного использования - неправильные memory_order могут привести к тонким багам

Для большинства случаев достаточно memory_order_seq_cst, но в высоконагруженных системах оптимизация с более слабыми порядками может дать выигрыш в производительности.