Какие примитивы синхронизации из стандартной библиотеки C++ ты использовал?

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

Ответ

В стандартной библиотеке C++ (заголовки <mutex>, <atomic>, <condition_variable>, <shared_mutex>) есть набор примитивов, которые я применяю в зависимости от задачи:

  • std::mutex и RAII-обертки (std::lock_guard, std::unique_lock): Это мой основной инструмент для защиты критических секций. Я почти всегда использую их вместе, чтобы избежать ручного вызова lock()/unlock().

    std::mutex dataMutex;
    std::vector<int> sharedData;
    
    void safePush(int value) {
        std::lock_guard<std::mutex> lock(dataMutex);
        sharedData.push_back(value);
    } // Мьютекс освобождается автоматически
  • std::unique_lock: Использую, когда нужна более гибкая блокировка, например, для условных переменных или отложенного захвата.

    std::mutex mtx;
    std::condition_variable cv;
    bool dataReady = false;
    
    void consumer() {
        std::unique_lock<std::mutex> ul(mtx);
        cv.wait(ul, []{ return dataReady; }); // Автоматически отпускает и снова захватывает мьютекс
        // Обработка данных
    }
  • std::atomic: Применяю для простых счетчиков, флагов или указателей, где нужна атомарность без полноценной блокировки. Это дает выигрыш в производительности для высоконагруженных участков.

    std::atomic<int> activeConnections{0};
    
    void onNewConnection() {
        ++activeConnections; // Атомарная операция
    }
  • std::condition_variable: Незаменим для реализации паттернов Producer-Consumer, когда потоку нужно ждать наступления какого-либо условия (например, появления задачи в очереди).

  • std::shared_mutex (C++17): Использую в сценариях "много читателей, один писатель" (read-write lock). Например, для защиты кэша конфигурации, который часто читается, но редко обновляется.

    std::shared_mutex cacheMutex;
    ConfigCache globalCache;
    
    std::string getConfig(const std::string& key) {
        std::shared_lock lock(cacheMutex); // Множество потоков могут читать одновременно
        return globalCache.get(key);
    }
    
    void updateConfig(const std::string& key, const std::string& value) {
        std::unique_lock lock(cacheMutex); // Только один писатель
        globalCache.set(key, value);
    }

Я избегаю использования std::recursive_mutex, так как его необходимость часто указывает на проблему с дизайном, и предпочитаю std::scoped_lock для deadlock-free захвата нескольких мьютексов.