2010-03-10 3 views
45

Я понимаю, что рекурсивный мьютекс позволяет блокировать мьютекс более одного раза, не заходя в тупик, и его нужно разблокировать столько же раз. Но в каких конкретных ситуациях вам нужно использовать рекурсивный мьютекс? Я ищу ситуации на уровне кода/кода.Когда использовать рекурсивный мьютекс?

+5

На самом деле не обман, а перекрытие: http://stackoverflow.com/questions/187761/recursive-lock-mutex-vs-non-recursive-lock-mutex. В этом вопросе спрашивается: «Почему бы кому-нибудь захотеть использовать нерекурсивный мьютекс». Этот вопрос спрашивает: «Почему бы кому-нибудь захотеть использовать рекурсивный мьютекс?». Это похоже на чужие цивилизации, сталкивающиеся ;-) –

+0

@Steve: Да, я действительно проверял этот поток раньше, но я просто не получил ответ, который искал ... Я действительно ищу конкретные проекты, где это действительно необходимо. – jasonline

+1

Я уверен, что их нет или, по крайней мере, нет простого и явно хорошего дизайна, чтобы служить в качестве приложений-убийц. Любая функция, которая принимает мьютекс, может быть заменена двумя функциями: одной, которая принимает ее, а другая - нет. Любая функция, которая хочет вызвать такую ​​функцию, может вызывать соответствующее в соответствии с тем, что уже существует мьютекс. Любая конструкция, в которой вы вызываете функции, не зная, действительно ли вы уже используете мьютекс, который имеет отношение к этой функции, вероятно, нарушен. Но посмотрите почти на любую Java, чтобы увидеть код, который получает краткость от рекурсивной блокировки. –

ответ

38

Например, если у вас есть функция, которая вызывает рекурсивно, и вы хотите, чтобы синхронизировать доступ к нему:

void foo() { 
    ... mutex_acquire(); 
    ... foo(); 
    ... mutex_release(); 
} 

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

void foo_entry() { 
    mutex_acquire(); foo(); mutex_release(); } 

void foo() { ... foo(); ... } 
+4

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

+3

@Michael: если ваша реализация мьютекса поддерживает рекурсивную блокировку вообще, скорее всего, она будет поддерживать ее эффективно. Все, что обычно требуется, заключается в том, что функция блокировки делает «if (mutex.owner == thisthread) {++ lockcount; return;}». Если у вас нет блокировки, вы читаете поле владельца несинхронизированным, но при условии, что реализация знает, что чтение и запись поля mutex.owner являются атомарными (например, чтение слов на x86), вы не можете получить false положительны. Если у вас есть блокировка, вы не можете получить ложный отрицательный результат, потому что ничто не может изменить владельца, пока вы его удерживаете. –

+0

«Вы не можете получить ложный позитив».Вероятно, я должен упомянуть, что есть некоторые дополнительные предположения - прежде всего, каждый раз, когда поток получает или освобождает мьютекс, ядро ​​гарантирует, что поле mutex.owner изменено таким образом, который видим для этого потока (либо измените его с нить или очистить кеш потока от него или что-то еще). –

1

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

Есть ли причина, чтобы не допустить мьютекс быть приобретенные несколько раз тем же потоком?

+1

Простота реализации, я думаю. Кроме того, здесь есть разумный фактор против рекурсивных мьютексов: http://stackoverflow.com/questions/187761/recursive-lock-mutex-vs-non-recursive-lock-mutex/293646#293646. Я подозреваю, что Java, возможно, в значительной степени уничтожила знакомство с средним программистом или возможность использования нерекурсивных блокировок. –

+0

C++ - это очень быстрый язык. В отличие от некоторых других популярных языков, C++ пытается обеспечить реализацию bare-bones (например, std :: mutex), которые выполняют свою основную работу как можно быстрее. Если вам нужно использовать более функциональный объект (например, std :: recursive_mutex) при небольшой стоимости накладных расходов, он также доступен. Дело в том, чтобы дать разработчику возможность писать невероятно быстрый код, когда ему это нужно. –

19

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

  • Если вы просто хотите, чтобы исключить другие темы, с помощью мьютекса защищенного ресурса, то вы можете использовать любой мьютекс типа, но, возможно, захотите использовать нерекурсивный семафор из-за его меньшей накладные расходы.
  • Если вы хотите вызывать функции рекурсивны, что запирать тот же мьютекс, то они либо
    • должны использовать один рекурсивный мьютекс или
    • должны разблокировать и снова заблокировать тот же нерекурсивный мьютекс и снова (остерегайтесь параллельных потоков!) (при условии, что это семантически звучит, все равно может быть проблема с производительностью), или
    • должны как-то аннотировать, какие мьютексы они уже заблокировали (имитируя рекурсивное владение/мьютексы).
  • Если вы хотите заблокировать несколько мьютексов объектов, защищенные от множества таких объектов, где наборы могли бы быть построены путем слияния, вы можете выбрать
    • использовать для каждого объекта точно один мьютекса , что позволяет более потоков работать параллельно, или
    • использовать для каждого объекта одного опорного к любому , возможно, совместно рекурсивного мьютекса, чтобы снизить вероятность не суметь заблокировать все семафор эс вместе, или
    • использовать для каждого объекта один сравнимые эталонного к любому , возможно, совместно нерекурсивен мьютексу, в обходе намерения заблокировать несколько раз.
  • Если вы хотите освободить блокировку в другом потоке, чем она была заблокирована, вам придется использовать нерекурсивные блокировки (или рекурсивные блокировки, которые явно позволяют это вместо бросать исключения).
  • Если вы хотите использовать переменные синхронизации, то вы должны быть в состоянии явно разблокировать мьютекс во время ожидания на любой переменной синхронизации, так что ресурс разрешается использовать в других потоках. Это можно сделать только с нерекурсивными мьютексами, поскольку рекурсивные мьютексы уже могут быть заблокированы вызывающей функцией текущей функции.
+4

Не уверен, что я согласен с тем, что «нужно разблокировать и заблокировать одни и те же нерекурсивные мьютексы снова и снова (остерегайтесь параллельных потоков!)» Является жизнеспособным даже с предупреждением. Если вы не готовы освободить ресурс (либо он находится в несогласованном состоянии, либо вы не хотите, чтобы кто-то еще обманывал его), выполните _not_ unlock/recur/lock. Я гарантирую, что в какой-то момент другой поток прокрадет вас и укусит вас, когда солнце не светит :-) Я не буду спускать вниз, так как остальная часть ответа на самом деле очень хорошая - я просто подумал, что я это придумаю. – paxdiablo

+1

См. Http://stackoverflow.com/questions/10546867/why-would-we-want-to-make-function-recursive-which-has-a-mutex -lock для того, что купило это. – paxdiablo

+1

и здесь: http://stackoverflow.com/q/10548284/462608 –

3

Если вы хотите увидеть пример кода, который использует рекурсивные мьютексы, посмотрите на источники для «Electric Fence» для Linux/Unix. «Twas - один из распространенных инструментов Unix для поиска« проверки границ »перерасходами и недоработками чтения и записи, а также с использованием освобожденной памяти до того, как появился Valgrind.

Просто скомпилируйте и подключите электрический забор с источниками (опция -g с помощью gcc/g ++), а затем соедините его с вашим программным обеспечением с помощью опции ссылки -lefence и начните выполнять вызовы в malloc/free. http://elinux.org/Electric_Fence

2

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

public void Process(...) 
{ 
    acquire_mutex(mMutex); 
    // Heavy processing 
    ... 
    reset(); 
    ... 
    release_mutex(mMutex); 
} 

public void reset() 
{ 
    acquire_mutex(mMutex); 
    // Reset 
    ... 
    release_mutex(mMutex); 
} 

Обе функции не должна выполняться одновременно, так как они изменяют внутренности класса, поэтому я хотел бы использовать семафор. Проблема в том, что процесс() вызывает reset() внутренне, и это создаст тупик, потому что mMutex уже получен. Блокировка их рекурсивным замком устраняет проблему.

+3

Просто создайте личную версию 'reset()', которая не будет блокировать мьютексы для внутреннего использования. Открытый API 'reset()' для блокировки мьютекса и вызова внутреннего сброса и разблокировки мьютекса. Это смехотворная причина введения рекурсивных мьютексов. Чтобы максимизировать параллелизм, вы должны держать замок как можно меньше времени, рекурсивные мьютексы не помогают с этим - наоборот. – FooF

+1

Как долго удерживать мьютекс в значительной степени зависит от задачи.Может быть, вы только код GUI; другие люди кодируют тяжелые элементы обработки, которые должны быть взаимно исключены. –

+0

Мьютекс - это убийцы, связанные с параллелизмом, вот в чем их суть. Как только ваши инварианты находятся в хорошей форме, вы разблокируете, чтобы другие тяжелые потоки обработки могли выполнять свою работу. См. Великий Дэвид Бутенхоф, говорящий о рекурсивных мьютексах: http://www.zaval.org/resources/library/butenhof1.html (их следует использовать только как «костыль», пока вы не получите безопасный код, не связанный с потоком, который будет реконфигурирован в потокобезопасном состояние с конкретными замками, он также считает, что в этом случае вам нужен только один глобальный замок). – FooF

Смежные вопросы