Стандартная библиотека C++<mutex>
В многопоточном программировании обеспечение согласованности данных и потокобезопасности имеет решающее значение.
Стандартная библиотека C++ предоставляет богатый набор примитивов синхронизации, используемых для контроля доступа к общим ресурсам.
В стандартной библиотеке C++,<mutex>Заголовочный файл предоставляет набор инструментов для реализации синхронизации и взаимного исключения между потоками в многопоточных программах.
<mutex>Заголовочный файл был представлен в C++11 и содержит классы и функции для мьютексных блокировок. Мьютексная блокировка — это механизм синхронизации, используемый для предотвращения одновременного доступа нескольких потоков к общим ресурсам.
Мьютекс — это примитив синхронизации, используемый для контроля доступа к общим ресурсам. Когда потоку требуется доступ к общему ресурсу, он пытается заблокировать мьютекс. Если мьютекс уже заблокирован другим потоком, запрашивающий поток будет заблокирован до тех пор, пока мьютекс не будет освобождён.
Базовый синтаксис
В C++,<mutex>Заголовочный файл предоставляет следующие основные классы:
std::mutex: Базовый мьютекс.std::recursive_mutex: Рекурсивный мьютекс, позволяющий одному и тому же потоку захватывать его несколько раз.std::timed_mutex: Мьютекс с поддержкой тайм-аута.std::recursive_timed_mutex: Рекурсивный мьютекс с поддержкой тайм-аута.
Пример
1. Использованиеstd::mutex
Ниже приведён простой пример, показывающий, как использовать в C++std::mutexдля синхронизации доступа к общим ресурсам.
Пример
#include <thread>
#include <mutex>
std::mutex mtx; // Глобальная блокировка мьютекса
int shared_resource = 0;
void increment() {
for (int i = 0; i < 10000; ++i) {
mtx.lock(); // Заблокировать мьютекс
++shared_resource;
mtx.unlock(); // Разблокировать мьютекс
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << "Итоговое значение shared_resource: " << shared_resource << std::endl;
return 0;
}
Результат вывода:
Final value of shared_resource: 20000
2. Использованиеstd::recursive_mutex
Рекурсивная блокировка мьютекса позволяет одному и тому же потоку блокировать один и тот же мьютекс несколько раз. Ниже приведён пример использованияstd::recursive_mutexПример.
Пример
#include <thread>
#include <mutex>
std::recursive_mutex rmtx; // Создать объект рекурсивного мьютекса
int shared_resource = 0; // Общий ресурс
// Рекурсивная функция
void recursive_increment(int count) {
if (count <= 0) return;
std::lock_guard<std::recursive_mutex> lock(rmtx); // Блокировка для обеспечения потокобезопасности
++shared_resource;
std::cout << "Значение shared_resource увеличено до " << shared_resource << " (count = " << count << ")" << std::endl;
// Рекурсивный вызов
recursive_increment(count - 1);
}
int main() {
std::thread t1(recursive_increment, 3); // Поток t1 выполняет recursive_increment(3)
std::thread t2(recursive_increment, 3); // Поток t2 выполняет recursive_increment(3)
t1.join(); // Ожидание завершения потока t1
t2.join(); // Ожидание завершения потока t2
std::cout << "Итоговое значение shared_resource: " << shared_resource << std::endl;
return 0;
}
Результат вывода:
Incremented shared_resource to 1 (count = 3) Incremented shared_resource to 2 (count = 2) Incremented shared_resource to 3 (count = 1) Incremented shared_resource to 4 (count = 0) Incremented shared_resource to 5 (count = 3) Incremented shared_resource to 6 (count = 2) Incremented shared_resource to 7 (count = 1) Incremented shared_resource to 8 (count = 0) Final value of shared_resource: 8
Анализ кода:
Создайте
std::recursive_mutexObject:std::recursive_mutex rmtx;Является рекурсивным мьютексом, который позволяет одному и тому же потоку захватывать блокировку несколько раз.Общий ресурс:
int shared_resource = 0;— это ресурс, к которому одновременно обращаются несколько потоков.Рекурсивная функция
recursive_increment:- Использование
std::lock_guard<std::recursive_mutex>ПараrmtxБлокировка. Поскольку используется рекурсивный мьютекс, один и тот же поток может захватить блокировку несколько раз. - Через
++shared_resourceУвеличивает значение общего ресурса. - Функция рекурсивно вызывает саму себя, демонстрируя, как рекурсивная функция безопасно работает при многократной блокировке.
- Использование
Создание и запуск потоков:
std::thread t1(recursive_increment, 3);иstd::thread t2(recursive_increment, 3);Создаются два отдельных потока, и оба будут выполнятьсяrecursive_increment(3)。t1.join()иt2.join()Ожидает завершения выполнения двух потоков.
Примечания
- Убедитесь, что после захвата каждой блокировки мьютекса выполняется операция разблокировки, чтобы избежать взаимной блокировки.
- Использование
std::lock_guardилиstd::unique_lockМожет автоматически управлять блокировкой и разблокировкой мьютексов, снижая вероятность ошибок. - Избегайте вызова функций, которые могут генерировать исключения, удерживая блокировку мьютекса, потому что это может привести к взаимной блокировке.
<mutex>Является очень важным заголовочным файлом в стандартной библиотеке C++, предоставляющим базовые механизмы синхронизации для многопоточного программирования. Используя блокировки мьютексов, мы можем обеспечить безопасный доступ к общим ресурсам, тем самым избегая гонок данных и проблем с несогласованностью.
Дополнительная информация
1. std::mutex
Предоставляет базовый мьютекс, чтобы гарантировать, что только один поток может одновременно обращаться к общим ресурсам.
Пример
std::mutex mtx;
void thread_function() {
std::lock_guard<std::mutex> lock(mtx);
// Доступ к общему ресурсу
}
2. std::recursive_mutex
Позволяет одному и тому же потоку многократно захватывать блокировку без возникновения взаимоблокировки, что особенно полезно для рекурсивных функций.
Пример
std::recursive_mutex rmtx;
void recursive_function(int count) {
if (count <= 0) return;
std::lock_guard<std::recursive_mutex> lock(rmtx);
// Рекурсивный вызов
recursive_function(count - 1);
}
3. std::timed_mutex
Предоставляет мьютекс с тайм-аутом, который позволяет задать время ожидания при попытке захватить блокировку.
Пример
#include <chrono>
std::timed_mutex tm;
void try_lock_for_example() {
if (tm.try_lock_for(std::chrono::seconds(1))) {
// Блокировка успешно получена
tm.unlock();
} else {
// Не удалось получить блокировку
}
}
4. std::recursive_timed_mutex
Наследуется от std::timed_mutex, позволяя одному и тому же потоку многократно получать блокировку и при этом поддерживая функции с тайм-аутом.
Пример
#include <chrono>
std::recursive_timed_mutex rtm;
void recursive_timed_function(int count) {
if (count <= 0) return;
if (rtm.try_lock_for(std::chrono::seconds(1))) {
// Блокировка успешно получена
recursive_timed_function(count - 1);
rtm.unlock();
} else {
// Не удалось получить блокировку
}
}
5. std::lock_guard
Обёртка, которая автоматически управляет блокировками std::mutex, используя стиль RAII, чтобы гарантировать автоматическое освобождение блокировки при выходе из области видимости.
Пример
std::mutex mtx;
void function() {
std::lock_guard<std::mutex> lock(mtx);
// Доступ к общему ресурсу
}
6. std::unique_lock
Обеспечивает более гибкое управление блокировками, чем std::lock_guard, позволяя вручную освобождать и повторно захватывать блокировки, а также поддерживает блокировку с таймаутом.
Пример
#include <chrono>
std::mutex mtx;
void function() {
std::unique_lock<std::mutex> lock(mtx);
// Доступ к общему ресурсу
// Можно вручную освободить блокировку
lock.unlock();
// Можно повторно захватить блокировку
lock.lock();
// Можно выполнить блокировку с таймаутом
if (lock.try_lock_for(std::chrono::seconds(1))) {
// Блокировка успешно получена
}
}
7. std::adopt_lock_t
Тип флага, используемый для указания того, что std::unique_lock перенимает существующую блокировку.
Пример
std::unique_lock<std::mutex> lock(mtx, std::adopt_lock);
8. std::defer_lock_t
Функция: тип флага, используемый для отложенной блокировки; при инициализации блокировка не захватывается.
Пример
std::unique_lock<std::mutex> lock(mtx, std::defer_lock);
// Можно вызвать lock.lock() позже
9. std::try_to_lock_t
Тип флага, используемый для попытки блокировки без блокирования.
Пример
std::unique_lock<std::mutex> lock1(mtx1, std::try_to_lock);
std::unique_lock<std::mutex> lock2(mtx2, std::try_to_lock);
if (lock1 && lock2) {
// Обе блокировки успешно захвачены
}