Какой умный указатель используешь в многопоточной среде?

«Какой умный указатель используешь в многопоточной среде?» — вопрос из категории Управление памятью, который задают на 25% собеседований C/C++ Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Выбор зависит от модели владения данными:

  1. std::unique_ptr<T> — для эксклюзивного владения, когда ресурс принадлежит только одному потоку в каждый момент времени. Сам по себе он не потокобезопасен для передачи владения между потоками без синхронизации. Однако его можно безопасно перемещать (std::move) в другой поток, если гарантируется, что старый поток больше к нему не обращается.

  2. std::shared_ptr<T> — для разделяемого владения несколькими потоками. Критически важно понимать: потокобезопасны только контрольный блок (счётчик ссылок) shared_ptr, но не данные, на которые он указывает. Несколько потоков могут безопасно создавать/уничтожать свои копии shared_ptr, указывающие на один объект.

// ПРАВИЛЬНО: Безопасное копирование shared_ptr между потоками
void reader(const std::shared_ptr<const Config>& config) {
    // Чтение config безопасно, если объект Config неизменяем (const)
}

// НЕПРАВИЛЬНО: Без синхронизации запись в общий объект через shared_ptr — data race
void unsafe_writer(std::shared_ptr<Config>& config) {
    config->value = 42; // ПОТЕНЦИАЛЬНЫЙ DATA RACE!
}

// ПРАВИЛЬНО: Защита данных мьютексом
struct ThreadSafeConfig {
    std::shared_ptr<Config> data;
    std::mutex mtx;
};
  1. std::atomic<std::shared_ptr<T>> (C++20) — предоставляет атомарные операции для замены одного shared_ptr на другой (например, load, store, exchange). Это полезно для реализаций типа lock-free RCU (Read-Copy-Update).

Итог: Сам по себе умный указатель не синхронизирует доступ к данным. Для изменяемых разделяемых данных всегда нужен дополнительный механизм синхронизации (мьютекс, атомарные переменные).