Когда использовать рекурсивный мьютекс? - PullRequest
53 голосов
/ 10 марта 2010

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

Ответы [ 5 ]

46 голосов
/ 10 марта 2010

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

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

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

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

void foo() { ... foo(); ... }
23 голосов
/ 02 апреля 2011

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

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

Сегодня я столкнулся с необходимостью рекурсивного мьютекса, и я думаю, что это, пожалуй, самый простой пример из опубликованных ответов: Это класс, который предоставляет две функции 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 уже получен. Блокировка их с помощью рекурсивной блокировки вместо этого решает проблему.

3 голосов
/ 27 апреля 2012

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

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

2 голосов
/ 10 марта 2010

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

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

...