← Назад ко всем вопросам

Что случится, если повторно заблокировать std::mutex в том же потоке

1️⃣ Как кратко ответить

Если повторно заблокировать std::mutex в том же потоке, произойдет взаимная блокировка (deadlock), так как std::mutex не поддерживает рекурсивное блокирование. Поток остановится, ожидая освобождения мьютекса, который он сам же и заблокировал.

2️⃣ Подробное объяснение темы

std::mutex — это примитив синхронизации в C++, который используется для управления доступом к общим ресурсам в многопоточных приложениях. Он обеспечивает эксклюзивный доступ к ресурсу, гарантируя, что только один поток может владеть мьютексом в любой момент времени.

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

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

Для решения этой проблемы, если требуется рекурсивное блокирование, следует использовать std::recursive_mutex, который позволяет одному и тому же потоку блокировать мьютекс несколько раз без взаимной блокировки. Однако, использование std::recursive_mutex может привести к более сложному управлению ресурсами и потенциальным ошибкам, поэтому его следует использовать с осторожностью.

Пример кода, демонстрирующий проблему:

#include <iostream>
#include <thread>
#include <mutex>
​
std::mutex mtx;
​
void problematicFunction() {
    mtx.lock(); // Поток блокирует мьютекс
    std::cout << "First lock acquired" << std::endl;
​
    // Попытка повторного блокирования того же мьютекса в том же потоке
    mtx.lock(); // Это приведет к взаимной блокировке
    std::cout << "Second lock acquired" << std::endl;
​
    mtx.unlock(); // Освобождение мьютекса
    mtx.unlock(); // Освобождение мьютекса
}
​
int main() {
    std::thread t(problematicFunction);
    t.join();
    return 0;
}
  • std::mutex mtx; — объявление мьютекса, который будет использоваться для синхронизации.
  • mtx.lock(); — поток блокирует мьютекс, получая эксклюзивный доступ к ресурсу.
  • std::cout << "First lock acquired" << std::endl; — вывод сообщения, подтверждающего, что мьютекс успешно заблокирован.
  • mtx.lock(); — попытка повторного блокирования мьютекса в том же потоке, что приводит к взаимной блокировке.
  • std::cout << "Second lock acquired" << std::endl; — эта строка никогда не будет выполнена из-за взаимной блокировки.
  • mtx.unlock(); — освобождение мьютекса, но до этой строки выполнение не дойдет из-за взаимной блокировки.

Для предотвращения взаимной блокировки в таких ситуациях можно использовать std::recursive_mutex:

#include <iostream>
#include <thread>
#include <mutex>
​
std::recursive_mutex rmtx;
​
void safeFunction() {
    rmtx.lock(); // Поток блокирует мьютекс
    std::cout << "First lock acquired" << std::endl;
​
    rmtx.lock(); // Повторное блокирование мьютекса в том же потоке
    std::cout << "Second lock acquired" << std::endl;
​
    rmtx.unlock(); // Освобождение мьютекса
    rmtx.unlock(); // Освобождение мьютекса
}
​
int main() {
    std::thread t(safeFunction);
    t.join();
    return 0;
}
  • std::recursive_mutex rmtx; — объявление рекурсивного мьютекса, который позволяет одному и тому же потоку блокировать его несколько раз.
  • rmtx.lock(); — поток блокирует мьютекс, получая эксклюзивный доступ к ресурсу.
  • std::cout << "First lock acquired" << std::endl; — вывод сообщения, подтверждающего, что мьютекс успешно заблокирован.
  • rmtx.lock(); — повторное блокирование мьютекса в том же потоке, что разрешено для std::recursive_mutex.
  • std::cout << "Second lock acquired" << std::endl; — вывод сообщения, подтверждающего, что повторное блокирование прошло успешно.
  • rmtx.unlock(); — освобождение мьютекса. Необходимо вызвать unlock() столько раз, сколько раз был заблокирован мьютекс.

Тема: Многопоточность / Синхронизация
Стадия: Tech

🔒 Подпишись на бусти автора и стань Алигатором, чтобы получить полный доступ к функционалу сайта и отслеживать свой прогресс!

Твои заметки