Я понимаю, что рекурсивный мьютекс позволяет блокировать мьютекс более одного раза, не переходя в тупик, и его нужно разблокировать столько же раз. Но в каких конкретных ситуациях вам нужно использовать рекурсивный мьютекс? Я ищу ситуации на уровне кода/кода.
Когда использовать рекурсивный мьютекс?
Ответ 1
Например, если у вас есть функция, которая вызывает его рекурсивно, и вы хотите получить к нему синхронизированный доступ:
void foo() {
... mutex_acquire();
... foo();
... mutex_release();
}
без рекурсивного мьютекса вам сначала нужно создать функцию "точки входа", и это становится громоздким, если у вас есть набор функций, которые являются взаимно рекурсивными. Без рекурсивного мьютекса:
void foo_entry() {
mutex_acquire(); foo(); mutex_release(); }
void foo() { ... foo(); ... }
Ответ 2
Рекурсивные и нерекурсивные мьютексы имеют различные варианты использования. Никакой тип мьютекса не может легко заменить другой. Нерекурсивные мьютексы имеют меньше накладных расходов, а рекурсивные мьютексы в некоторых ситуациях полезны или даже необходимы семантике, а в других ситуациях - опасная или даже сломанная семантика. В большинстве случаев кто-то может заменить любую стратегию, используя рекурсивные мьютексы, с другой более безопасной и более эффективной стратегией, основанной на использовании нерекурсивных мьютексов.
- Если вы просто хотите исключить другие потоки из вашего ресурса, защищенного мьютексом, вы можете использовать любой тип mutex, но может захотеть использовать нерекурсивный мьютекс из-за его меньших накладных расходов.
- Если вы хотите рекурсивно вызывать функции, которые блокируют один и тот же мьютекс, то они либо
- должны использовать один рекурсивный мьютекс или
- нужно разблокировать и заблокировать одни и те же нерекурсивные мьютексы снова и снова (остерегайтесь параллельных потоков!) (при условии, что это семантически звучит, это может быть проблема с производительностью) или
- должны каким-то образом аннотировать, какие мьютексы они уже заблокировали (имитируя рекурсивное владение/мьютексы).
- Если вы хотите заблокировать несколько объектов, защищенных мьютексом, из набора таких объектов, где комплекты могли быть созданы путем слияния, вы можете выбрать
- для использования на одном объекте ровно один мьютекс, что позволяет больше потоков работать параллельно или
- использовать для каждого объекта одну ссылку для любого , возможно, рекурсивного мьютекса, чтобы снизить вероятность отказа от блокировки всех мьютексов или
- использовать для одного объекта одну сопоставимую ссылку для любого возможно разделяемого нерекурсивного мьютекса, обходя намерение блокировать несколько раз.
- Если вы хотите освободить блокировку в другом потоке, кроме того, что она была заблокирована, вам нужно использовать нерекурсивные блокировки (или рекурсивные блокировки, которые явно разрешают это, а не бросают исключения).
- Если вы хотите использовать переменные синхронизации, тогда вам нужно быть способным явно разблокировать мьютекс, ожидая любой переменной синхронизации, чтобы ресурс разрешался использовать в других потоках. Это возможно только с помощью нерекурсивных мьютексов, поскольку рекурсивные мьютексы уже могут быть заблокированы вызывающей функцией текущей функции.
Ответ 3
Если вы хотите увидеть пример кода, который использует рекурсивные мьютексы, посмотрите на источники для "Electric Fence" для Linux/Unix. "Twas - один из распространенных инструментов Unix для поиска" проверки границ "перерасхода и перезаписи чтения/записи, а также использования освобожденной памяти до Valgrind.
Просто скомпилируйте и подключите электрический забор с источниками (опция -g с gcc/g++), а затем привяжите его к вашему программному обеспечению с помощью опции ссылки - освободителю и начните переходить через вызовы в malloc/free. http://elinux.org/Electric_Fence
Ответ 4
Сегодня я столкнулся с необходимостью рекурсивного мьютекса, и, по-моему, это самый простой пример среди опубликованных ответов: Это класс, который предоставляет две функции 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);
}
Обе функции не должны запускаться одновременно, потому что они изменяют внутренности класса, поэтому я хотел использовать мьютекс. Проблема в том, что процесс() вызывает reset() внутренне, и это создаст тупик, потому что mMutex уже получен. Вместо этого блокировка с помощью рекурсивного замка устраняет проблему.
Ответ 5
Это было бы проблемой, если бы поток блокировал попытку получения (снова) уже существующего мьютекса...
Есть ли причина, по которой не допускается одновременное получение мьютекса одним и тем же потоком?