Lock re-accquision in sync forbiden?

1,041 views
Skip to first unread message

josvazg

unread,
May 10, 2015, 10:25:20 AM5/10/15
to golan...@googlegroups.com
I was surprised by sync lock behavior today:

It seems sync.*Lock does not allow lock re-acquisition by the same goroutine that already has the lock.

Why is that so?

Sample: the following program deadlocks.
Bigupdate represents a big update on a shared "something" that reuses code from a smaller update (smallupdate) that also needs locking if called alone.

http://play.golang.org/p/bCtXkdyuV5

package main

import (
    "fmt"
    "sync"
)

var mutex sync.Mutex

func smallUpdate() {
    mutex.Lock()
    defer mutex.Unlock()
    // do something small
}

func bigUpdate() {
    mutex.Lock()
    defer mutex.Unlock()
    // do something
    smallUpdate()
    // do something else
}

func main() {
    bigUpdate()
    fmt.Println("Hello, playground")
}


bronze man

unread,
May 10, 2015, 11:04:00 AM5/10/15
to golan...@googlegroups.com
I also have come across this problem.
You should wrap your smallUpdate function with a lock version and a lock free version.
It looks like golang do not have any user data on a goroutine. The lock seems do not anything about which goroutine is holding the lock.

Staven

unread,
May 10, 2015, 5:37:00 PM5/10/15
to golan...@googlegroups.com
On Sun, May 10, 2015 at 07:25:20AM -0700, josvazg wrote:
> I was surprised by sync lock behavior today:
>
>
> *It seems sync.*Lock does not allow lock re-acquisition by the same
> goroutine that already has the lock.*

Locks in Go are not reentrant [http://en.wikipedia.org/wiki/Reentrant_mutex].
Goroutines don't "have" locks. Locks are simply locked or unlocked.
Calling m.Lock() on a locked mutex blocks until m.Unlock() is called,
it doesn't matter what goroutine called m.Lock() previously.
You could even have completely different goroutines lock and unlock
the mutex (thought it's probably not a very good idea in general).

Ian Lance Taylor

unread,
May 11, 2015, 1:20:43 PM5/11/15
to josvazg, golang-nuts
On Sun, May 10, 2015 at 7:25 AM, josvazg <jos...@gmail.com> wrote:
>
> I was surprised by sync lock behavior today:
>
> It seems sync.*Lock does not allow lock re-acquisition by the same goroutine
> that already has the lock.
>
> Why is that so?

A mutex implements an invariant. The invariant is true while the
mutex is not held. It's OK to break the invariant while the mutex is
held, as you update whatever state is protected by the mutex. You
must restore the invariant before you release the mutex.

If you re-acquire a mutex that you have already acquired, you can not
know the state of the invariant (if you do know, then you also know
that the mutex has already been acquired, and you have no need to
re-acquire it). Therefore, you can not safely modify the shared
state.

As a general rule, re-acquiring mutexes does not make sense. The C++
std::recursive_mutex type is a bad idea.

Ian

Carlos Castillo

unread,
May 11, 2015, 1:38:49 PM5/11/15
to golan...@googlegroups.com, jos...@gmail.com
Although it does sort of mention it in unlock (http://golang.org/pkg/sync/#Mutex.Unlock) it might be a good idea to document on the mutex itself that Mutexes:
  • Can be locked or unlocked by anyone (are not associated with/to a goroutine)
  • Are not recursive, so locking a mutex twice in the same Goroutine is no different than then behaviour of doing it in two separate routines, eg: the second call will block.
It sort of sucks that most people assume that locks are recursive first, despite the fact that it is harder/slower to implement those locks correctly, and is easier to allow through subtle bugs. 

josvazg

unread,
May 16, 2015, 2:14:54 AM5/16/15
to golan...@googlegroups.com, jos...@gmail.com
Agreed.

In my case I was spoiled by the way Java handles this. Java locks are reentrant and I sort of thought there was some kind of "ownership" associated with locking in general.

I do now understand that non-reentrant locks are:
  1. Safer, as they force you to be more careful and avoid nesting locking which you should not do anyway and can get tricky very easily, specially if you lock different things at once without noticing.
  2. Faster, as the implementation does not need to account for ownership or reentrancy.
Thanks for your replies. But as Carlos I do think we should explain in the sync package that Go locks are NOT reentrant a maybe even explain why, as there are reasons for it.

Jose
Reply all
Reply to author
Forward
0 new messages