Go's sync
package has a Mutex
. Unfortunately it's not recursive. What's the best way to implement recursive locks in Go?
-
4[Here are Russ Cox's, core Go team member, thoughts on recursive locks](https://groups.google.com/d/msg/golang-nuts/XqW1qcuZgKg/Ui3nQkeLV80J). He doesn't like them! – Nick Craig-Wood Feb 03 '13 at 10:19
-
I created a library that does allow for counting/recursive locks. Give it a try: github.com/jwells131313/goethe – jwells131313 Apr 04 '18 at 17:36
3 Answers
I'm sorry to not answer your question directly:
IMHO, the best way how to implement recursive locks in Go is to not implement them, but rather redesign your code to not need them in the first place. It's probable, I think, that the desire for them indicates a wrong approach to some (unknown here) problem is being used.
As an indirect "proof" of the above claim: Would a recursive lock be a common/correct approach to the/some usual situations involving mutexes, it would be sooner or later included in the standard library.
And finally, last but not least: What Russ Cox from the Go development team wrote here https://groups.google.com/d/msg/golang-nuts/XqW1qcuZgKg/Ui3nQkeLV80J:
Recursive (aka reentrant) mutexes are a bad idea. The fundamental reason to use a mutex is that mutexes protect invariants, perhaps internal invariants like "p.Prev.Next == p for all elements of the ring", or perhaps external invariants like "my local variable x is equal to p.Prev."
Locking a mutex asserts "I need the invariants to hold" and perhaps "I will temporarily break those invariants." Releasing the mutex asserts "I no longer depend on those invariants" and "If I broke them, I have restored them."
Understanding that mutexes protect invariants is essential to identifying where mutexes are needed and where they are not. For example, does a shared counter updated with atomic increment and decrement instructions need a mutex? It depends on the invariants. If the only invariant is that the counter has value i - d after i increments and d decrements, then the atmocity of the instructions ensures the invariants; no mutex is needed. But if the counter must be in sync with some other data structure (perhaps it counts the number of elements on a list), then the atomicity of the individual operations is not enough. Something else, often a mutex, must protect the higher-level invariant. This is the reason that operations on maps in Go are not guaranteed to be atomic: it would add expense without benefit in typical cases.
Let's take a look at recursive mutexes. Suppose we have code like this:
func F() {
mu.Lock()
... do some stuff ...
G()
... do some more stuff ...
mu.Unlock()
}
func G() {
mu.Lock()
... do some stuff ...
mu.Unlock()
}
Normally, when a call to mu.Lock returns, the calling code can now assume that the protected invariants hold, until it calls mu.Unlock.
A recursive mutex implementation would make G's mu.Lock and mu.Unlock calls be no-ops when called from within F or any other context where the current thread already holds mu. If mu used such an implementation, then when mu.Lock returns inside G, the invariants may or may not hold. It depends on what F has done before calling G. Maybe F didn't even realize that G needed those invariants and has broken them (entirely possible, especially in complex code).
Recursive mutexes do not protect invariants. Mutexes have only one job, and recursive mutexes don't do it.
There are simpler problems with them, like if you wrote
func F() {
mu.Lock()
... do some stuff
}
you'd never find the bug in single-threaded testing. But that's just a special case of the bigger problem, which is that they provide no guarantees at all about the invariants that the mutex is meant to protect.
If you need to implement functionality that can be called with or without holding a mutex, the clearest thing to do is to write two versions. For example, instead of the above G, you could write:
// 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()
}
or if they're both unexported, g and gLocked.
I am sure that we'll need TryLock eventually; feel free to send us a CL for that. Lock with timeout seems less essential but if there were a clean implementation (I don't know of one) then maybe it would be okay. Please don't send a CL that implements recursive mutexes.
Recursive mutexes are just a mistake, nothing more than a comfortable home for bugs.
Russ

- 87,403
- 16
- 175
- 139
-
An alternative pattern (when needing to provide synchronization) might be to use Go channels. [Here is an introduction](http://www.golang-book.com/10/index.htm). – Dan Esparza Apr 18 '15 at 13:15
-
10Your "proof" begs the question: Erik has a problem recursive locking might solve, but it's not yet available in the standard library. We don't need to add support, because if it were needed, it'd sooner or later be added. – Patrick Jun 02 '16 at 00:24
-
17This motivation is so off. It is not the mutex responsibility to preserve the invariants. Its responsibility is to prevent mutual access (and the name says it all) at the time invariants are broken. It is your responsibility to make sure that invariants are preserved when the mutex is unlocked, as it is your responsibility in case of a recursive mutex to make sure the invariants are intact when moving from method to method, because ... it is not the mutex responsibility. – gsf Jun 18 '16 at 15:18
-
1I created a library that does allow for counting/recursive locks. Give it a try: https://github.com/jwells131313/goethe – jwells131313 Apr 04 '18 at 17:34
-
3Assume a general purpose library providing function `func F(callback func())`, which locks a mutex and then calls the callback. Of course `callback` is not allowed to call `F` again. But if user by accident does? Deadlock. Rather difficult to discover, if other go routines are still running. A recursive mutex might be used to detect this situation easily and then panic instead (which Go's mutex doesn't do), immediately making the problem obvious to the user. – Aconcagua Nov 10 '20 at 11:45
-
Go's `defer` clause which should be used for unlocks is what creates the need for an reentrant lock in the first place, because with that I'm not allowed to call an unlock manually at any other place in the method (or the `defer` clause will panic). So a methods which reads something with a rlock and in some cases delegates to another method to write something with a wlock won't work, I have to choose between a deadlock or a panic due to a double unlock (manual unlock + `defer` unlock) here. – NotX May 12 '21 at 09:31
-
@NotX You CAN call unlock in another place then lock again, if needed. The *locked methods are the way you do it. – Paul Stelian Jul 18 '21 at 10:27
-
@PaulStelian Might be a misunderstanding. My issue is not the re-usabilty of locks in a successive manner (i.e. lock-unlock, lock-unlock), but that locks might get unlocked twice, once "manually" in a conditional clause, and once in the `defer` clause (i.e. lock-unlock-unlock). – NotX Jul 19 '21 at 11:51
-
@NotX I usually just do unlock, recursive call, then lock again so that the defer clause has something to unlock. Yes, it's a bit weird, but it can be done. And of course the simpler option of having *locked functions that don't do anything on their own, that just works. – Paul Stelian Jul 19 '21 at 16:47
-
3
-
Downvote. The answer is incomplete and not helpful. The problems described by Russ Cox are theoretical, they hardly matter in practice. But there are scenarios (usually involving callbacks, recursion, etc.) that are very hard or impossible to implement with non-reentrant locks. In Java and C#, locks are reentrant. POSIX, Python and many others offer reentrant locks. Russ Cox seems to think the designers of all of these languages and libraries are wrong. I'd say Russ Cox's thinking about this question is too simplistic. – jcsahnwaldt Reinstate Monica Nov 13 '22 at 12:26
You could quite easily make a recursive lock out of a sync.Mutex and a sync.Cond. See Appendix A here for some ideas.
Except for the fact that the Go runtime doesn't expose any notion of goroutine Id. This is to stop people doing silly things with goroutine local storage, and probably indicates that the designers think that if you need a goroutine Id you are doing it wrong.
You can of course dig the goroutine Id out of the runtime with a bit of C if you really want to. You might want to read that thread to see why the designers of Go think it is a bad idea.

- 52,955
- 12
- 126
- 132
as was already established, this is a miserable, horrible, awful, and terrible idea from a concurrency perspective.
Anyway, since your question is really about Go's type system, here's how you would define a type with a recursive method.
type Foo struct{}
func (f Foo) Bar() { fmt.Println("bar") }
type FooChain struct {
Foo
child *FooChain
}
func (f FooChain) Bar() {
if f.child != nil {
f.child.Bar()
}
f.Foo.Bar()
}
func main() {
fmt.Println("no children")
f := new(FooChain)
f.Bar()
for i := 0; i < 10; i++ {
f = &FooChain{Foo{}, f}
}
fmt.Println("with children")
f.Bar()
}

- 8,064
- 4
- 36
- 35
-
18I guess then all languages that support recursive locks (and that's most of them) are miserable, horrible, awful and terrible. – Feb 03 '13 at 17:12
-
1not necessarily, it's just terrible with regards to managing concurrency in Go. What's the problem you're trying to solve? – jorelli Feb 03 '13 at 17:14
-
@ErikAigner locks are just usually slower than other concurrent design constructs. – Dan Esparza Apr 18 '15 at 13:16