Стандартная библиотека 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 <iostream>
#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 <iostream>
#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

Анализ кода:

  1. Создайтеstd::recursive_mutexObject:std::recursive_mutex rmtx;Является рекурсивным мьютексом, который позволяет одному и тому же потоку захватывать блокировку несколько раз.

  2. Общий ресурс:int shared_resource = 0;— это ресурс, к которому одновременно обращаются несколько потоков.

  3. Рекурсивная функцияrecursive_increment:

    • Использованиеstd::lock_guard<std::recursive_mutex>ПараrmtxБлокировка. Поскольку используется рекурсивный мьютекс, один и тот же поток может захватить блокировку несколько раз.
    • Через++shared_resourceУвеличивает значение общего ресурса.
    • Функция рекурсивно вызывает саму себя, демонстрируя, как рекурсивная функция безопасно работает при многократной блокировке.
  4. Создание и запуск потоков:

    • 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

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

Пример

#include <mutex>

std::mutex mtx;

void thread_function() {
    std::lock_guard<std::mutex> lock(mtx);
    // Доступ к общему ресурсу
}

2. std::recursive_mutex

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

Пример

#include <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 <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 <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, чтобы гарантировать автоматическое освобождение блокировки при выходе из области видимости.

Пример

#include <mutex>

std::mutex mtx;

void function() {
    std::lock_guard<std::mutex> lock(mtx);
    // Доступ к общему ресурсу
}

6. std::unique_lock

Обеспечивает более гибкое управление блокировками, чем std::lock_guard, позволяя вручную освобождать и повторно захватывать блокировки, а также поддерживает блокировку с таймаутом.

Пример

#include <mutex>
#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::mutex mtx;
std::unique_lock<std::mutex> lock(mtx, std::adopt_lock);

8. std::defer_lock_t

Функция: тип флага, используемый для отложенной блокировки; при инициализации блокировка не захватывается.

Пример

std::mutex mtx;
std::unique_lock<std::mutex> lock(mtx, std::defer_lock);
// Можно вызвать lock.lock() позже

9. std::try_to_lock_t

Тип флага, используемый для попытки блокировки без блокирования.

Пример

std::mutex mtx1, mtx2;
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) {
    // Обе блокировки успешно захвачены
}
Другие расширения