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

Ответ

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

  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).

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

Ответ 18+ 🔞

А, ну ты про это! Слушай, тут вообще-то всё просто, как три копейки, но если накосячить — будет тебе хиросима и нигерсраки, я тебе скажу. Разбираемся по косточкам, что к чему.

Первый случай, std::unique_ptr<T>. Это как твоя личная зажигалка в кармане. Эксклюзивное владение, ёпта. Только ты её трогаешь, и пока она у тебя, никто другой до неё не дотронется. Сам по себе этот указатель — не какая-то волшебная мартышлюшка, он не умеет телепортироваться между потоками сам. Но! Его можно передать. Сделал std::move — и всё, чувак, он теперь в другом потоке, а у тебя в руках пустота. Главное правило — как передал, так и забудь, не пытайся потом из старого потока к нему прикоснуться, а то получишь undefined behavior, а это, считай, вилкой в глаз. Волнение ебать, но логика железная.

Второй, std::shared_ptr<T>. Вот тут уже начинается цирк. Это как общая пачка сигарет на столе в курилке. Идея в том, что много потоков могут на неё указывать. И вот тут главный подвох, который все проёбывают: потокобезопасен только счётчик ссылок в контрольном блоке, а не сами данные! То есть, безопасно создавать копии этого указателя из разных потоков — счётчик атомарно увеличится-уменьшится, всё окей. Но если полезешь менять то, на что он указывает, без защиты — это чистый data race, пидарас шерстяной.

Смотри, вот тебе наглядняк:

// ТАК МОЖНО: Все потоки просто читают неизменяемые (const) данные.
void reader(const std::shared_ptr<const Config>& config) {
    // Читаем спокойно, объект не меняется.
}

// ТАК НЕЛЬЗЯ: Один поток пишет, другой читает — бардак, гонка данных.
void unsafe_writer(std::shared_ptr<Config>& config) {
    config->value = 42; // Вот тут-то тебя и накроют медным тазом! DATA RACE!
}

// ТАК НАДО: Данные защищаем отдельно, хоть мьютексом, хоть чем.
struct ThreadSafeConfig {
    std::shared_ptr<Config> data;
    std::mutex mtx; // Вот эта хитрая жопа всех и спасает.
};

Третий, std::atomic<std::shared_ptr<T>> (C++20). Это уже для продвинутых, когда хочется lock-free магии. Он позволяет атомарно подменить один shared_ptr на другой во всём процессе. Полезно для каких-нибудь хитрых обновлений на лету. Но, опять же, это про замену самого указателя, а не про доступ к данным внутри!

Итог, бля, железный: Умный указатель — это про владение памятью, ёбать копать, а не про синхронизацию доступа к ней. Хоть unique_ptr, хоть shared_ptr — если данные общие и изменяемые, тебе в любом случае нужен мьютекс, атомарная переменная или другая защита. Иначе доверия ебать ноль будет к твоей программе. Запомни это, и живи спокойно.