← Назад к вопросам
Какие знаешь Lock Guard в стандартной библиотеке?
2.0 Middle🔥 191 комментариев
#Многопоточность и синхронизация
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Lock Guard в стандартной библиотеке C++
Что такое Lock Guard
Lock Guard — это шаблоны классов из <mutex>, которые реализуют RAII-паттерн (Resource Acquisition Is Initialization) для управления блокировками. Они автоматически захватывают мьютекс при создании и освобождают при уничтожении.
1. std::lock_guard (C++11)
Самый простой и часто используемый lock guard. Не позволяет переносить или копировать блокировку.
#include <mutex>
#include <iostream>
#include <thread>
std::mutex m;
int counter = 0;
void incrementCounter() {
// std::lock_guard захватывает мьютекс при конструировании
std::lock_guard<std::mutex> lock(m);
// Критическая секция
counter++;
std::cout << "Счётчик: " << counter << std::endl;
// Мьютекс автоматически освобождается когда lock выходит из области видимости
} // lock_guard деструктор: unlock()
int main() {
std::thread t1(incrementCounter);
std::thread t2(incrementCounter);
t1.join();
t2.join();
return 0;
}
Преимущества std::lock_guard
// ✅ Безопасность при исключениях
std::lock_guard<std::mutex> lock(m);
try {
dangerousOperation(); // Может выбросить исключение
} // Мьютекс всё равно будет освобождён
// ❌ Если бы мы делали это без lock_guard:
std::mutex::lock();
try {
dangerousOperation(); // Выброшено исключение
} // Мьютекс НИКОГДА не будет разблокирован!
std::mutex::unlock();
2. std::unique_lock (C++11)
Более гибкий lock guard с дополнительными возможностями:
#include <mutex>
#include <iostream>
std::mutex m;
int value = 0;
void example1() {
// Стандартное использование (как lock_guard)
std::unique_lock<std::mutex> lock(m);
value = 10;
} // Разблокировка
void example2() {
// Отложенный захват
std::unique_lock<std::mutex> lock(m, std::defer_lock);
// Сейчас мьютекс НЕ захвачен
std::cout << "Перед захватом" << std::endl;
lock.lock(); // Явный захват
std::cout << "Во время захвата" << std::endl;
lock.unlock(); // Явное освобождение
std::cout << "После освобождения" << std::endl;
} // Деструктор вызовется, но мьютекс уже разблокирован
void example3() {
// Пробование захватить мьютекс с таймаутом
std::unique_lock<std::timed_mutex> lock(m);
// Используется timed_mutex вместо обычного mutex
}
void example4() {
// Условная переменная работает с unique_lock
std::condition_variable cv;
std::unique_lock<std::mutex> lock(m);
cv.wait(lock, []() { return value > 0; });
// Внутри wait() мьютекс временно освобождается
}
int main() {
example1();
example2();
return 0;
}
Конструкторы std::unique_lock
| Конструктор | Описание |
|---|---|
unique_lock(m) | Захватывает мьютекс сразу |
unique_lock(m, defer_lock) | НЕ захватывает мьютекс |
unique_lock(m, try_to_lock) | Пробует захватить без блокирования |
unique_lock(m, adopt_lock) | Принимает уже захваченный мьютекс |
Методы std::unique_lock
std::unique_lock<std::mutex> lock(m, std::defer_lock);
// Захват и освобождение
lock.lock(); // Захватить
lock.unlock(); // Освободить
bool ok = lock.try_lock(); // Попробовать захватить без ожидания
// Проверка состояния
if (lock.owns_lock()) { // Захватил ли мьютекс?
std::cout << "Владею мьютексом" << std::endl;
}
// Передача владения (move semantics)
std::unique_lock<std::mutex> lock2 = std::move(lock);
// Теперь lock2 владеет мьютексом, а lock больше не владеет
3. std::shared_lock (C++14)
Для ситуаций, когда несколько потоков могут одновременно читать, но только один может писать:
#include <shared_mutex>
#include <iostream>
#include <thread>
#include <vector>
class Database {
private:
std::shared_mutex mtx;
std::vector<int> data;
public:
// Чтение: несколько потоков одновременно
int read(int index) const {
std::shared_lock<std::shared_mutex> lock(mtx);
return data[index];
}
// Запись: только один поток
void write(int index, int value) {
std::unique_lock<std::shared_mutex> lock(mtx);
data[index] = value;
}
};
int main() {
Database db;
// Много потоков могут читать одновременно
std::thread reader1([&db]() { std::cout << db.read(0) << std::endl; });
std::thread reader2([&db]() { std::cout << db.read(0) << std::endl; });
// Писать может только один поток
std::thread writer([&db]() { db.write(0, 42); });
reader1.join();
reader2.join();
writer.join();
return 0;
}
4. std::scoped_lock (C++17)
Модернизированная версия для захвата нескольких мьютексов без дедлока:
#include <mutex>
std::mutex m1, m2, m3;
// Старый способ (опасен для дедлока)
void oldWay() {
std::lock_guard<std::mutex> lock1(m1);
std::lock_guard<std::mutex> lock2(m2); // Может дедлокнуться
}
// Новый способ (безопасен от дедлока)
void newWay() {
// Автоматически захватывает все мьютексы в правильном порядке
std::scoped_lock lock(m1, m2, m3);
// Все три мьютекса захвачены, и дедлока не будет
} // Все три мьютекса освобождаются
Сравнение Lock Guard типов
| Класс | Можно освобождать | Можно передавать | Условные переменные | Нескольким мьютексам | Когда использовать |
|---|---|---|---|---|---|
| lock_guard | Нет | Нет | Нет | Один | Простые случаи |
| unique_lock | Да | Да (move) | Да | Один | Гибкие сценарии |
| shared_lock | Нет | Нет | Нет | Один (shared) | Читатели-писатели |
| scoped_lock | Нет | Нет | Нет | Несколько | Несколько мьютексов |
Практический пример: Безопасный счётчик
#include <mutex>
#include <iostream>
#include <thread>
#include <vector>
class ThreadSafeCounter {
private:
mutable std::mutex mtx;
int value = 0;
public:
void increment() {
std::lock_guard<std::mutex> lock(mtx);
value++;
}
int getValue() const {
std::lock_guard<std::mutex> lock(mtx);
return value;
}
};
int main() {
ThreadSafeCounter counter;
std::vector<std::thread> threads;
// Создаём 10 потоков, каждый инкрементирует счётчик 1000 раз
for (int i = 0; i < 10; i++) {
threads.emplace_back([&counter]() {
for (int j = 0; j < 1000; j++) {
counter.increment();
}
});
}
// Ждём все потоки
for (auto& t : threads) {
t.join();
}
std::cout << "Финальное значение: " << counter.getValue() << std::endl; // 10000
return 0;
}
Практический пример: Читатели-писатели
#include <shared_mutex>
#include <string>
#include <iostream>
class SharedData {
private:
mutable std::shared_mutex mtx;
std::string data;
public:
// Много потоков могут читать одновременно
std::string read() const {
std::shared_lock<std::shared_mutex> lock(mtx);
return data;
}
// Только один поток может писать
void write(const std::string& newData) {
std::unique_lock<std::shared_mutex> lock(mtx);
data = newData;
}
};
Ключевые моменты
- std::lock_guard — самый простой и быстрый для базовых случаев
- std::unique_lock — универсальный с дополнительными возможностями
- std::shared_lock — для паттерна читателей-писателей
- std::scoped_lock — для нескольких мьютексов без дедлока
- RAII гарантирует, что мьютекс освобождается в любом случае
- Lock Guard запрещает копирование, но unique_lock позволяет перемещение