Когда использовать рекурсивный мьютекс?

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

5 ответов


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

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

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

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

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

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

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

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

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

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

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


Если вы хотите увидеть пример кода, который использует рекурсивные мьютексы, посмотрите на источники "электрического забора" для Linux/Unix. Это был один из распространенных инструментов Unix для поиска "проверки границ" перерасхода чтения/записи и перерасхода, а также использования освобожденной памяти до отчет пришли вместе.

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


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

есть ли причина не разрешать мьютекс приобретаться несколько раз одним и тем же потоком?