Ответ 1
Извините, что не ответил на ваш вопрос напрямую:
IMHO, лучший способ реализации рекурсивных блокировок в Go - не реализовать их, а скорее перепроектировать ваш код, чтобы они не нуждались в них в первую очередь. Вероятно, я думаю, что стремление к ним указывает на неправильный подход к какой-то (неизвестной здесь) проблеме.
Как косвенное "доказательство" вышеуказанного утверждения: будет ли рекурсивная блокировка обычным/правильным подходом к/некоторым обычным ситуациям, связанным с мьютексами, он рано или поздно включается в стандартную библиотеку.
И, наконец, последнее, но не менее важное: что Russ Cox из команды разработчиков Go написал здесь https://groups.google.com/d/msg/golang-nuts/XqW1qcuZgKg/Ui3nQkeLV80J:
Рекурсивные (так называемые реентерабельные) мьютексы - плохая идея. Основной причиной использования мьютекса является то, что мьютексы защищать инварианты, возможно, внутренние инварианты типа "p.Prev.Next == p для всех элементов кольца", или, возможно, внешние инварианты типа "моя локальная переменная x равна p.Prev."
Блокировка мьютекса утверждает: "Мне нужны инварианты для хранения" и, возможно, "я временно нарушу эти инварианты". Освобождение мьютекса утверждает: "Я больше не зависим от тех инвариантов" и "Если я сломаю их, я их восстановил".
Понимание того, что мьютексы защищают инварианты, имеет важное значение для определяя, где мьютексы необходимы, а где нет. Например, общий счетчик, обновленный с помощью атомного Инструкции по приращению и декременту нуждаются в мьютексе? Это зависит от инвариантов. Если единственным инвариантом является то, что счетчик имеет значение я - d после я приращений и d декрементов, то ограниченность инструкций обеспечивает инварианты; нет мьютекса. Но если счетчик должен быть в синхронизации с какой-либо другой структурой данных (возможно, она подсчитывает количество элементов в списке), то атомарность отдельных операций недостаточно. Что-то другое, часто мьютекс, должен защищать инвариант более высокого уровня. Именно по этой причине операции на картах в Go не являются гарантированно будет атомарным: это добавит расходы без в типичных случаях.
Посмотрим на рекурсивные мьютексы. Предположим, что у нас есть такой код:
func F() {
mu.Lock()
... do some stuff ...
G()
... do some more stuff ...
mu.Unlock()
}
func G() {
mu.Lock()
... do some stuff ...
mu.Unlock()
}
Обычно, когда вызов mu.Lock возвращается, вызывающий код теперь можно считать, что защищенные инварианты сохраняются до тех пор, пока он вызывает mu.Unlock.
Рекурсивная реализация мьютекса сделает G mu.Lock и mu.Unlock вызовы будут no-ops при вызове из F или любой другой контекст, в котором текущий поток уже содержит mu. Если mu использовала такую реализацию, тогда, когда mu.Lock возвращается внутри G, инварианты могут или не могут выполняться. Это зависит на том, что сделал F до вызова G. Возможно, F даже не осознавал что G нуждался в этих инвариантах и сломал их (полностью возможно, особенно в сложном коде).
Рекурсивные мьютексы не защищают инварианты. Мьютексы имеют только одно задание, а рекурсивные мьютексы этого не делают.
С ними возникают более простые проблемы, например, если вы написали
func F() {
mu.Lock()
... do some stuff
}
вы никогда не найдете ошибку в однопоточном тестировании. Но это всего лишь частный случай большой проблемы, что они не предоставляют никаких гарантий вообще о инварианты, которые мьютекс призван защищать.
Если вам нужно реализовать функциональность, которая может быть вызвана с мьютексом или без него, самая ясная вещь состоит в том, чтобы написать две версии. Например, вместо приведенного выше G, вы можете написать:
// To be called with mu already held.
// Caller must be careful to ensure that ...
func g() {
... do some stuff ...
}
func G() {
mu.Lock()
g()
mu.Unlock()
}
или если они оба не экспортированы, g и gLocked.
Я уверен, что в конечном итоге нам понадобится TryLock; не стесняйся отправьте нам CL для этого. Блокировка с тайм-аутом кажется менее существенной но если бы была чистая реализация (я не знаю одного) тогда, возможно, все будет хорошо. Пожалуйста, не отправляйте CL, чтобы реализует рекурсивные мьютексы.
Рекурсивные мьютексы - это просто ошибка, не более удобный дом для ошибок.
Расс