Рекурсивные и нерекурсивные блокировки (мьютекс)

У меня проблемы с блокировкой в ​​моей программе. Итак, я читал о блокировках, но проблема в том, что большая часть информации противоречива или не определяется платформой. На Рекурсивная блокировка (мьютекс) против нерекурсивной блокировки (мьютекс) самый принятый ответ говорит:

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

В комментариях люди говорят, что это не правильно, и нет никаких упоминаний об этом. Так…

1) Если я заблокирую нерекурсивный мьютекс в потоке A. Может ли поток B разблокировать его, не захватывая блокировку?

2) Если поток был взят в нерекурсивном мьютексе потоком A и поток B вызывает, чтобы получить блокировку, будет ли поток B ждать, пока блокировка не будет снята, чтобы получить блокировку, или он вызовет исключение? Как насчет этого случая в рекурсивном мьютексе? (Также обсуждается в других вопросах, по которым нельзя сделать приличный вывод)

3) При использовании рекурсивных блокировок, при завершении процесса все ли рекурсивные блокировки должны быть сняты? (В зависимости от того, где заканчивается процесс, чего не происходит)

4) Какие проблемы я наблюдаю при использовании комбинации рекурсивных и нерекурсивных блокировок с осторожностью?

PS: используя только платформу Windows и std::thread,

2

Решение

Я думаю тебе поможет очень читая вики на Reentrant Mutexes. Я согласен с комментариями в этой другой ветке; Принятый ответ неверен или, по крайней мере, очень плохо объясняет его точку зрения.

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

Так в чем же разница между рекурсивным / реэнрантным и обычным мьютексом? Рекурсивный мьютекс может быть заблокирован несколько раз одним потоком. Цитировать вики:

Рекурсивные блокировки (также называемые мьютексом рекурсивного потока) — это те, которые позволяют потоку рекурсивно получить тот же замок, который он удерживает. Обратите внимание, что это поведение отличается от обычной блокировки. В нормальном случае, если поток, который уже удерживает нормальную блокировку, пытается получить такую ​​же блокировку снова, он будет заблокирован.

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

На самом деле это только причина использовать рекурсивный мьютекс; В большинстве других ситуаций, когда один и тот же поток пытается получить одну и ту же блокировку без ее освобождения, вероятно, можно реорганизовать правильное получение / освобождение блокировки без необходимости рекурсивного мьютекса. И делать это будет намного безопаснее; Рекурсивная функция, естественно, будет пузыриться и освобождать каждую блокировку на рекурсивном мьютексе, предполагая RAII, где, как и в других ситуациях, вы можете не освободить мьютекс в достаточной степени и все же оказаться в тупике.

Итак, чтобы ответить на ваши конкретные вопросы:

  1. Нет, если ваша система мьютекса специально не позволяет
  2. Да, в обоих случаях в целом, хотя, опять же, это специфическая реализация мьютекса в отношении блокировки / выброса. Почти в каждой системе, которую я когда-либо использовал, просто блоки (и именно поэтому нерекурсивные взаимные блокировки взаимоблокируются, если один и тот же поток блокируется дважды без освобождения)
  3. Да, обычно, хотя обычно они будут освобождены при условии надлежащего RAII, и процесс завершится изящно. Процессы, которые завершаются незаметно при удерживании замков, могут быть чем-то вроде громкого удара.
  4. Смотрите мои объяснения выше. В частности, обратите внимание на следующее из вики:

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

6

Другие решения

Вы имеете в виду обсуждение мьютексов POSIX, но Windows все равно не поддерживает POSIX, и вы используете стандартные примитивы потоков C ++, которые могут отличаться в деталях.

Поэтому лучше сначала проверить документацию стандартной библиотеки, например, стандарт c ++ для unlock прямо заявляет:

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

1

Ниже приводится справочная страница Linux pthread_mutex_lock,

Переменные типа pthread_mutex_t также могут быть статически инициализированы с использованием констант PTHREAD_MUTEX_INITIALIZER (для быстрых мьютексов), THREAD_RECURSIVE_MUTEX_INITIALIZER_NP (для рекурсивных мьютексов) и PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP (для проверки ошибок мьютексов).

На error checking'' andрекурсивные » мьютексы, pthread_mutex_unlock фактически проверяет во время выполнения, что мьютекс заблокирован на входе и что он был заблокирован тем же потоком, который теперь вызывает pthread_mutex_unlock. Если эти условия не выполняются, возвращается код ошибки и мьютекс остается неизменным. « Быстрые » мьютексы не выполняют таких проверок, что позволяет разблокировать заблокированный мьютекс потоком, отличным от его владельца. Это непереносимое поведение, на которое нельзя полагаться.

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

1

На самом деле, вы должны написать простую программу для проверки этих случаев.

  1. Предполагая, что мы правильно используем мьютекс, другой поток никогда не должен разблокировать мьютекс. Любой поток может иметь возможность разблокировать мьютекс. (редактировать: я никогда не пытался разблокировать мьютекс с другим потоком. Это победило бы цель) Это вызвало бы условия гонки.

  2. Рассмотрим код:

    void criticalSection(){
    pthread_mutex_lock(&mutex)
    //dostuff
    pthread_mutex_unlock(&mutex)
    }
    

    Если есть два потока, поток A и B, и поток A сначала входит в функцию, он получает блокировку и выполняет работу. Если поток B входит, когда поток A все еще находится в функции, он будет заблокирован. Когда поток A выполняется

      pthread_mutex_unlock(&mutex)
    

    Нить B теперь будет «разбужена» и начнет делать вещи.

  3. Я полагаю, что вы можете делать все, что захотите, но если есть рекурсивные блокировки, это означает, что поток все еще что-то делает, и вы, вероятно, хотите дождаться его завершения.

  4. Я полагаю, вы смотрите на многопоточное приложение без условий гонки. 🙂

0
По вопросам рекламы [email protected]