Skip to content

goroutine mutual exclusion (aka recursive mutex for Go)

License

Notifications You must be signed in to change notification settings

xaionaro-go/gorex

Repository files navigation

GoDoc go report Build Status Coverage Status

CC0

gorex

gorex == GORoutine mutual EXclusion

This package implements Mutex and RWMutex. They are similar to sync.Mutex and sync.RWMutex, but they track which goroutine locked the mutex and will not cause a deadlock if the same goroutine will try to lock the same mutex again.

type myEntity struct {
    gorex.Mutex
}

func (ent *myEntity) func1() {
    ent.Lock()
    defer ent.Unlock()

    .. some stuff ..
    ent.func2() // will not get a deadlock here!
    .. other stuff ..
}

func (ent *myEntity) func2() {
    ent.Lock()
    defer ent.Unlock()

    .. more stuff ..
}

The same in other syntax:

type myEntity struct {
    gorex.Mutex
}

func (ent *myEntity) func1() {
    ent.LockDo(func() {
        .. some stuff ..
        ent.func2() // will not get a deadlock here!
        .. other stuff ..
    })
}

func (ent *myEntity) func2() {
    ent.LockDo(func(){
        .. more stuff ..
    })
}
locker := &goroutine.RWMutex{}

locker.RLockDo(func() {
    .. do some read-only stuff ..
    if cond {
      return
    }
    locker.LockDo(func() { // will not get a deadlock here!
        .. do write stuff ..
    })
})

But...

But you still will get a deadlock if you do this way:

var locker = &gorex.RWMutex{}

func someFunc() {
    locker.RLockDo(func() {
        .. do some read-only stuff ..
        if cond {
          return
        }
        locker.LockDo(func() { // you will get a deadlock here!
            .. do write stuff ..
        })
    })
}()

func main() {
    go someFunc()
    go someFunc()
}

because there could be a situation that a resource is blocked by a RLockDo from both goroutines and both goroutines waits (on LockDo) until other goroutine will finish RLockDo. But still you will easily see the reason of deadlocks due to LockDo-s in the call stack trace.

Benchmark

It's essentially slower than bare sync.Mutex/sync.RWMutex:

goos: linux
goarch: amd64
pkg: github.com/xaionaro-go/gorex
Benchmark/Lock-Unlock/single/sync.Mutex-8         	77933413	        15.2 ns/op	       0 B/op	       0 allocs/op
Benchmark/Lock-Unlock/single/sync.RWMutex-8       	46052574	        26.1 ns/op	       0 B/op	       0 allocs/op
Benchmark/Lock-Unlock/single/Mutex-8              	20281420	        58.6 ns/op	       0 B/op	       0 allocs/op
Benchmark/Lock-Unlock/single/RWMutex-8            	13518639	        87.1 ns/op	       0 B/op	       0 allocs/op
Benchmark/Lock-Unlock/parallel/sync.Mutex-8       	10836991	       111 ns/op	       0 B/op	       0 allocs/op
Benchmark/Lock-Unlock/parallel/sync.RWMutex-8     	 9065725	       133 ns/op	       0 B/op	       0 allocs/op
Benchmark/Lock-Unlock/parallel/Mutex-8            	 9425310	       123 ns/op	       2 B/op	       0 allocs/op
Benchmark/Lock-Unlock/parallel/RWMutex-8          	 5309696	       213 ns/op	       4 B/op	       0 allocs/op
Benchmark/RLock-RUnlock/single/sync.RWMutex-8     	76609815	        15.2 ns/op	       0 B/op	       0 allocs/op
Benchmark/RLock-RUnlock/single/RWMutex-8          	25071478	        47.9 ns/op	       0 B/op	       0 allocs/op
Benchmark/RLock-RUnlock/parallel/sync.RWMutex-8   	25705654	        48.3 ns/op	       0 B/op	       0 allocs/op
Benchmark/RLock-RUnlock/parallel/RWMutex-8        	14786738	        80.9 ns/op	       0 B/op	       0 allocs/op
Benchmark/Lock-ed:Lock-Unlock/single/Mutex-8      	31392260	        38.2 ns/op	       0 B/op	       0 allocs/op
Benchmark/Lock-ed:Lock-Unlock/single/RWMutex-8    	32588916	        37.6 ns/op	       0 B/op	       0 allocs/op
Benchmark/RLock-ed:RLock-RUnlock/single/RWMutex-8 	26416754	        46.1 ns/op	       0 B/op	       0 allocs/op
Benchmark/RLock-ed:RLock-RUnlock/parallel/RWMutex-8         	13113901	        88.7 ns/op	       0 B/op	       0 allocs/op
PASS
ok  	github.com/xaionaro-go/