ПрограммированиеC++ многопоточный/серверный разработчик

Какие нюансы нужно учитывать при работе с многопоточностью в C++? Опишите разницу между std::mutex, std::lock_guard, std::unique_lock и приведите пример безопасной работы с потоками.

Проходите собеседования с ИИ помощником Hintsage

Ответ

В современном C++ (начиная с C++11) существует богатый инструментарий для работы с многопоточностью: std::thread, мьютексы и различные объекты-обёртки для управления доступом к ресурсам. Безопасное взаимодействие между потоками требует понимания следующих компонентов:

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

Чтобы избежать гонок, всегда используйте RAII-обёртки там, где возможно. Учтите, что попытка повторно захватить тот же мьютекс приведёт к deadlock (если это не рекурсивный мьютекс).

Пример корректной работы:

#include <iostream> #include <mutex> #include <thread> std::mutex mtx; void printThreadSafe(int id) { std::lock_guard<std::mutex> lock(mtx); std::cout << "Thread " << id << std::endl; } int main() { std::thread t1(printThreadSafe, 1); std::thread t2(printThreadSafe, 2); t1.join(); t2.join(); }

Вопрос с подвохом

Можно ли использовать std::lock_guardstd::mutex для реализации условной синхронизации с std::condition_variable?

Ответ: Нет! Для работы с std::condition_variable обязательно нужен std::unique_lockstd::mutex, потому что lock_guard не предоставляет методов для контроля блокировки (например, временно отпустить — unlock — и потом снова захватить), которые необходимы при ожидании уведомления.

Пример:

std::mutex mtx; std::condition_variable cv; bool ready = false; void waitForEvent() { std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, []{return ready;}); // Дальнейшая обработка }

Примеры реальных ошибок из-за незнания тонкостей темы


История

В проекте использовалась ручная работа с mtx.lock()/mtx.unlock(). При выбрасывании исключения между lock/unlock мьютекс оставался заблокированным — множество потоков "подвисали". После замены на RAII (std::lock_guard) проблема исчезла.


История

При оповещении через std::condition_variable вместо std::unique_lock использовали std::lock_guard. В результате, программа не компилировалась на новых компиляторах, а на старом — происходил runtime assert из-за несогласованного API.


История

В коде вызывался unlock() у std::lock_guard для "ручного" контроля доступа к мьютексу. Это приводило к неопределённому поведению на разных компиляторах, иногда к падению приложения. Было понято, что lock_guard не поддерживает manual release, нужна migration к unique_lock.