4

This code (a single threaded program) will never work:

func TestDoubleLockPanics(t *testing.T) {
    var mu sync.Mutex
    mu.Lock()
    mu.Lock()
}

However, when I run this test, there's no panic. The race detector does not print out a data race. go vet does not complain, there's no log message, it just blocks forever.

(The actual real-life code I care about is, obviously, not this simple - I've just boiled it down to the essence.)

Is there any way to get Go to tell me, loudly, when a thread that holds a lock attempts to re-acquire the same lock?

Kevin Burke
  • 61,194
  • 76
  • 188
  • 305

1 Answers1

5

Go's sync.Mutex objects do not keep track of which goroutine locked them. Or, to put it another way, there isn't a locking thread: there is just a data lock.

Moreover, as the comment just above Unlock says, you can unlock them from some other goroutine than the one that locked them. Hence it can actually make sense to call Lock twice. Here's a skeleton example:

func f() {
    mu.Lock()
    ... if this is not just an example, some code goes here ...
    // now wait for g() to call mu.Unlock()
    mu.Lock()
    fmt.Println("we got here, so g() must have done an unlock")
    mu.Unlock()
}

func g() {
    ... some code probably goes here too ...
    mu.Unlock() // allow f() to proceed
    ... more code, perhaps ...
}

In this case we would not want a panic in f's second Lock call.

You can build your own recursive locks or other techniques, but since the internal ID of each goroutine is hidden, it's pretty tricky. There's a link to an implementation in the comments at this answer to Recursive locking in Go, but I've never used it, or even looked closely at it.

torek
  • 448,244
  • 59
  • 642
  • 775