Рекурсивные и нерекурсивные блокировки (мьютекс)
У меня проблемы с блокировкой в моей программе. Итак, я читал о блокировках, но проблема в том, что большая часть информации противоречива или не определяется платформой. В рекурсивной блокировке (Mutex) против нерекурсивной блокировки (Mutex) наиболее приемлемый ответ гласит:
Поскольку рекурсивный мьютекс имеет чувство собственности, поток, который захватывает мьютекс, должен быть тем же потоком, который освобождает мьютекс. В случае нерекурсивных мьютексов чувство владения отсутствует, и любой поток обычно может освободить мьютекс независимо от того, какой поток изначально захватил мьютекс. Во многих случаях этот тип "мьютекса" на самом деле является скорее действием семафора, когда вы не обязательно используете мьютекс в качестве устройства исключения, но используете его в качестве устройства синхронизации или сигнализации между двумя или более потоками.
В комментариях люди говорят, что это не правильно, и нет никаких упоминаний об этом. Так...
1) Если я блокирую нерекурсивный мьютекс в потоке A. Может ли поток B разблокировать его, не захватывая блокировку?
2) Если поток был взят в нерекурсивном мьютексе потоком A и поток B вызывает, чтобы получить блокировку, будет ли поток B ждать, пока блокировка не будет снята, чтобы получить блокировку, или он вызовет исключение? Как насчет этого случая в рекурсивном мьютексе? (Также обсуждается в других вопросах, где нельзя сделать достойного заключения)
3) При использовании рекурсивных блокировок, при завершении процесса все ли рекурсивные блокировки должны быть сняты? (В зависимости от того, где заканчивается процесс, чего не происходит)
4) Какие проблемы я наблюдаю при использовании комбинации рекурсивных и нерекурсивных блокировок с осторожностью?
PS: используя только платформу Windows и std::thread
,
4 ответа
Я думаю, что вам очень поможет чтение вики о Reentrant Mutexes. Я согласен с комментариями в этой другой ветке; Принятый ответ неверен или, по крайней мере, очень плохо объясняет его точку зрения.
Все мьютексы имеют понятие собственности. Вот что отличает их от семафора. Поток, который блокирует мьютекс, всегда является потоком, который должен его разблокировать, и это является частью того, почему мьютексы могут вызвать взаимоблокировку, а также почему они работают по своему прямому назначению (чтобы взаимно исключить доступ к определенному блоку кода).
Так в чем же разница между рекурсивным / реэнрантным и обычным мьютексом? Рекурсивный мьютекс может быть заблокирован несколько раз одним и тем же потоком. Цитировать вики:
Рекурсивные блокировки (также называемые мьютексом рекурсивного потока) - это те, которые позволяют потоку рекурсивно получить тот же замок, который он удерживает. Обратите внимание, что это поведение отличается от обычной блокировки. В обычном случае, если поток, который уже удерживает нормальную блокировку, пытается получить такую же блокировку снова, он будет заблокирован.
В этом вся разница между двумя типами мьютексов. По сути, вам нужен рекурсивный мьютекс, если вы помещаете блокировку мьютекса в рекурсивный метод и метод рекурсивно перед освобождением мьютекса. В противном случае после первой рекурсии возникнет мгновенная тупиковая ситуация, поскольку блокировка не может быть получена во второй раз.
На самом деле, это единственная причина использовать рекурсивный мьютекс; В большинстве других ситуаций, когда один и тот же поток пытается получить одну и ту же блокировку без ее освобождения, вероятно, можно реорганизовать правильное получение / освобождение блокировки без необходимости рекурсивного мьютекса. И делать это будет намного безопаснее; Рекурсивная функция, естественно, будет пузыриться и освобождать каждую блокировку рекурсивного мьютекса, предполагая RAII, где, как и в других ситуациях, вы не можете в достаточной степени освободить мьютекс и все же оказаться в тупике.
Итак, чтобы ответить на ваши конкретные вопросы:
- Нет, если ваша система мьютекса специально не позволяет
- Да, в обоих случаях в целом, хотя, опять же, это специфическая реализация мьютекса в отношении блокировки / выброса. Почти в каждой системе, которую я когда-либо использовал, просто блоки (и именно поэтому нерекурсивные взаимные блокировки взаимоблокируются, если один и тот же поток блокируется дважды без освобождения)
- Да, обычно, хотя обычно они будут освобождены при условии надлежащего RAII, и процесс завершится изящно. Процессы, которые завершаются незаметно при удерживании замков, могут быть чем-то вроде громкого удара.
- Смотрите мои объяснения выше. В частности, обратите внимание на следующее из вики:
Обратите внимание, что рекурсивная блокировка считается снятой тогда и только тогда, когда количество ее полученных совпадений совпадает с количеством ее сбросов потоком-владельцем.
Ниже приводится справочная страница Linux pthread_mutex_lock,
Переменные типа pthread_mutex_t также могут быть статически инициализированы с использованием констант PTHREAD_MUTEX_INITIALIZER (для быстрых мьютексов), THREAD_RECURSIVE_MUTEX_INITIALIZER_NP (для рекурсивных мьютексов) и PTHREAD_ERRORCHECNITE_ZER_INEX_TURING_Ex_Ex_REX_TIN_EXER_Ex_REX_TIN_EX_E_REX_TEX_TEX_TEX_EX_EX_EXTEXT_TEX_TEX_TURING для проверки (
На error checking'' and
рекурсивные '' мьютексы, pthread_mutex_unlock фактически проверяет во время выполнения, что мьютекс заблокирован на входе и что он был заблокирован тем же потоком, который теперь вызывает pthread_mutex_unlock. Если эти условия не выполняются, возвращается код ошибки и мьютекс остается неизменным. `` Быстрые '' мьютексы не выполняют таких проверок, что позволяет разблокировать заблокированный мьютекс потоком, отличным от его владельца. Это непереносимое поведение, на которое нельзя полагаться.
Похоже, что "заблокированный мьютекс может быть разблокирован потоком, отличным от его владельца, с нерекурсивным видом мьютекса"
Вы имеете в виду обсуждение мьютексов POSIX, но Windows все равно не поддерживает POSIX, и вы используете стандартные примитивы потоков C++, которые могут отличаться в деталях.
Поэтому лучше сначала проверить документацию стандартной библиотеки, например, стандарт C++ для unlock
прямо заявляет:
Требуется: вызывающий поток должен владеть мьютексом.
На самом деле, вы должны написать простую программу для проверки этих случаев.
Предполагая, что мы правильно используем мьютекс, другой поток никогда не должен разблокировать мьютекс. Любой поток может иметь возможность разблокировать мьютекс. (редактировать: я никогда не пытался разблокировать мьютекс с другим потоком. Это победило бы цель) Это вызвало бы условия гонки.
Рассмотрим код:
void criticalSection(){ pthread_mutex_lock(&mutex) //dostuff pthread_mutex_unlock(&mutex) }
Если есть два потока, поток A и B, и поток A сначала входит в функцию, он получает блокировку и выполняет работу. Если поток B входит, когда поток A все еще находится в функции, он будет заблокирован. Когда поток A выполняется
pthread_mutex_unlock(&mutex)
Нить B теперь будет "разбужена" и начнет делать вещи.
Я полагаю, вы можете делать все, что хотите, но если есть рекурсивные блокировки, это означает, что поток все еще что-то делает, и вы, вероятно, хотите дождаться его завершения.
Я полагаю, вы смотрите на многопоточное приложение без условий гонки.:-)