Reputation: 3230
I have a recursive function that I will lock and mutate its internal state, however, it causes a deadlock. How can I implement such a recursive function without deadlock?
package main
import (
"fmt"
"sync"
)
type runner struct {
sync.Mutex
value int
}
func (r *runner) decrement() {
r.Lock()
defer r.Unlock()
if r.value == 0 {
return
}
r.value--
r.decrement()
}
func main() {
r := runner{
value: 10,
}
r.decrement()
fmt.Printf("r: %v\n", r.value)
}
Expect running the above code would print r: 0
, but actually got deadlock:
fatal error: all goroutines are asleep - deadlock!
Upvotes: 1
Views: 1396
Reputation: 62668
The typical solution to this kind of problem is a reentrant mutex. However, Russ Cox of the Go team has a good argument as to why reentrant mutexes are a bad idea.
In this case, you don't want to defer your unlock. Instead, you should lock and unlock the minimal sections necessary.
func (r *runner) decrement() {
r.Lock()
if r.value == 0 {
r.Unlock()
return
}
r.value--
r.Unlock()
r.decrement()
}
This solves two problems: one, it (marginally) improves the ability of your code to run concurrently by not taking locks for things which don't need locking, and two, it ensures that if you re-enter decrement()
, there isn't an outstanding lock that will result in a deadlock.
Upvotes: 1