← Назад к вопросам

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

2.0 Middle🔥 241 комментариев
#Умные указатели и управление памятью#Многопоточность и синхронизация

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

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

В многопоточной среде правильный выбор умного указателя критически важен для безопасности и производительности.

std::shared_ptr — потокобезопасный счёт ссылок

std::shared_ptr — основной выбор для многопоточности:

#include <memory>
#include <thread>
#include <vector>

std::shared_ptr<int> shared_data = std::make_shared<int>(42);

std::vector<std::thread> threads;
for (int i = 0; i < 10; ++i) {
    threads.emplace_back([shared_data]() {
        // Безопасно! Счёт ссылок синхронизирован
        std::cout << *shared_data << std::endl;
    });
}

for (auto& t : threads) t.join();
// shared_data удалится только когда все потоки его отпустят

Преимущества:

  • Потокобезопасный счёт ссылок — атомарные операции на уровне счётчика
  • Объект не удаляется, пока на него есть хотя бы одна ссылка
  • Идеален для передачи данных между потоками

Недостатки:

  • Overhead на счётчик — два выделения памяти (сам объект + блок контроля)
  • Атомарные операции — медленнее обычного increment/decrement
  • На очень高 нагрузке может стать узким местом

std::unique_ptr — для одного владельца

std::unique_ptr используется когда данные принадлежат одному потоку:

class Worker {
    std::unique_ptr<DataBuffer> buffer;
public:
    Worker() : buffer(std::make_unique<DataBuffer>()) {}
    
    void process() {
        // Только этот поток (владелец) работает с buffer
        buffer->append("data");
    }
};

std::thread worker_thread(&Worker::process, &worker);

Преимущества:

  • Нулевой overhead — никаких счётчиков
  • Максимальная производительность
  • Ясная семантика владения

Ограничение:

  • Нельзя передавать между потоками (нет копирования)
  • Может быть перемещён (move), но это требует синхронизации

Сравнение подходов

СценарийРекомендацияПричина
Данные совместно используютсяstd::shared_ptrБезопасность и управление жизненным циклом
Данные в одном потокеstd::unique_ptrНет overhead
Передача собственностиstd::unique_ptr с moveЯвная передача ответственности
Защита от race conditionsstd::shared_ptr + std::mutexДополнительная синхронизация

Лучшие практики

1. Используй std::make_shared для оптимизации:

// Хорошо: одно выделение памяти
auto ptr = std::make_shared<MyClass>(args);

// Плохо: два выделения
std::shared_ptr<MyClass> ptr(new MyClass(args));

2. Защищай доступ мьютексом, если нужна синхронизация:

std::shared_ptr<int> data;
std::mutex mtx;

{
    std::lock_guard<std::mutex> lock(mtx);
    *data = 42;  // Безопасно модифицировать
}

3. Избегай циклических ссылок (если есть слабые указатели):

class Node {
    std::shared_ptr<Node> next;      // Сильная ссылка
    std::weak_ptr<Node> previous;    // Слабая ссылка (не увеличивает счёт)
};

4. Передавай shared_ptr по значению в потоки:

std::shared_ptr<Data> shared_data = std::make_shared<Data>();

std::thread t([shared_data]() {  // Захватываем по значению
    // shared_data живёт пока работает лямбда
});

Итог

Для многопоточности std::shared_ptr — стандартный выбор благодаря потокобезопасному счёту ссылок. Используй std::unique_ptr когда данные не нужны в других потоках. Всегда комбинируй с std::mutex для синхронизации доступа к самим данным.

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