Может ли std::recursive_mutex когда-либо вызвать тупик?

Я знаю, что если я заблокирую std::mutex дважды в одном потоке, это приведет к тупику.

Итак, я глобально заменил std::mutex на std::recursive_mutex.

Означает ли использование только std::recursive_mutex, что я никогда не столкнусь с тупиком?

Есть ли с этим какая-либо потенциальная проблема (кроме того, что он немного медленнее)?

Это связано с Будет ли recursive_mutex блокироваться при блокировке двумя потоками?, но этот вопрос касается случая, когда есть только один мьютекс, так что это не обман.

Scott McPeak 15.06.2024 07:28
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
1
98
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

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

Ответ принят как подходящий

recursive_mutex решает проблему только тогда, когда потенциальный тупик вызван 1 потоком, которому требуется доступ к 1 защищенному ресурсу.

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

Рассмотрим следующий сценарий, включающий два потока, которым требуется монопольный доступ к двум ресурсам (каждый из которых защищен мьютексом):

  1. ThreadA блокирует RecursiveMutexA, чтобы получить доступ к ResourceA.
  2. ThreadB блокирует RecursiveMutexB, чтобы получить доступ к ResourceB.
  3. ThreadA пытается заблокировать RecursiveMutexB, чтобы получить доступ к ResourceB, и происходит взаимоблокировка.
  4. ThreadB пытается заблокировать RecursiveMutexA, чтобы получить доступ к ResourceA, и происходит взаимоблокировка.

Классическое решение этого сценария состоит в том, что все потоки должны блокировать мьютексы в одном и том же порядке.
В качестве альтернативы, если это применимо в вашем случае, вы можете использовать std::lock для одновременной блокировки нескольких блокировок.
Более поздний подход может быть намного быстрее, как показано в статье Говарда Хиннанта: Обеденные философы: перезагрузка .

Или, если блокировки можно заблокировать в одном и том же месте кода, используйте std::lock . Эта статья показывает, что std::lock может быть значительно быстрее, чем наведение порядка.

Howard Hinnant 15.06.2024 16:28

@HowardHinnant, спасибо, добавил эту информацию в свой ответ.

wohlstad 15.06.2024 16:33

Различные приказы или условия могут быть вашими врагами. Рассмотрим следующее (я использовал namespace std, чтобы навести порядок, хотя, как правило, это не лучшая практика):

#include <iostream>
#include <thread>
#include <mutex>

using namespace std;
recursive_mutex firstOne, secondOne;

void threadingOne() {
    lock_guard<recursive_mutex> lock1(firstOne);

    //pretend stuff happens
    this_thread::sleep_for(chrono::milliseconds(100));

    lock_guard<recursive_mutex> lock2(secondOne);
}

void threadingTwo() {
    lock_guard<recursive_mutex> lock2(secondOne);

    //this is stuff happening
    this_thread::sleep_for(chrono::milliseconds(100));

    lock_guard<recursive_mutex> lock1(firstOne);
}

int main() {
    thread threadOne(threadingOne);
    thread threadTwo(threadingTwo);
    threadOne.join();
    threadTwo.join();

    cout << "This is not something you will see ever\n";
    return 0;
}

Другие вопросы по теме

Похожие вопросы