import (
"fmt"
"runtime"
"strconv"
"strings"
"sync"
)
type ReentrantLock struct {
mu *sync.Mutex
cond *sync.Cond
owner int
holdCount int
}
func NewReentrantLock() sync.Locker {
rl := &ReentrantLock{}
rl.mu = new(sync.Mutex)
rl.cond = sync.NewCond(rl.mu)
return rl
}
func GetGoroutineId() int {
defer func() {
if err := recover(); err != nil {
fmt.Println("panic recover:panic info:%v", err) }
}()
var buf [64]byte
n := runtime.Stack(buf[:], false)
idField := strings.Fields(strings.TrimPrefix(string(buf[:n]), "goroutine "))[0]
id, err := strconv.Atoi(idField)
if err != nil {
panic(fmt.Sprintf("cannot get goroutine id: %v", err))
}
return id
}
func (rl *ReentrantLock) Lock() {
me := GetGoroutineId()
rl.mu.Lock()
defer rl.mu.Unlock()
if rl.owner == me {
rl.holdCount++
return
}
for rl.holdCount != 0 {
rl.cond.Wait()
}
rl.owner = me
rl.holdCount = 1
}
func (rl *ReentrantLock) Unlock() {
rl.mu.Lock()
defer rl.mu.Unlock()
if rl.holdCount == 0 || rl.owner != GetGoroutineId() {
panic("illegalMonitorStateError")
}
rl.holdCount--
if rl.holdCount == 0 {
rl.cond.Signal()
}
}
分析 :
1. 单goroutine调用Lock(),Unlock()多层嵌套,类似
lock()
lock()
lock()
...
unlock()
unlock()
unlock()
第一次调用lock()时,rl.owner会被设置为当前goroutine且rl.holdCount设置为1,之后的第2…n次嵌套调用只会增加rl.holdCount, 即
if rl.owner == me {
rl.holdCount++
return
}
同时当调用Unlock()时会递减rl.holdCount
- 多个goroutine程情况
上面分析了单goroutine下的执行情况,在多goroutine下则相应的比较简单,当第一个goroutine调用lock()时,rl.holdCount被设置为1,之后其他goroutine再调用lock()时由于rl.holdCount!=0会释放自己持有的rl.mu锁,即
for rl.holdCount != 0 {
rl.cond.Wait() // 释放持有的rl.mu锁
}
最终第一个调用lock()goroutine会再次获得rl.mu锁并使rl.holdCount递减,当rl.holdCount==0时通知其它goroutine来竞争调用lock()。
if rl.holdCount == 0 {
rl.cond.Signal() // 随机选取一个goroutine来执行lock()
}
测试代码
type LockStruct struct {
Mu sync.Locker
name string
id int
}
func (s *LockStruct) setName(name string) {
s.Mu.Lock()
defer s.Mu.Unlock()
s.name = name
}
func (s *LockStruct) setId(id int) {
s.Mu.Lock()
defer s.Mu.Unlock()
s.id = id
}
func (s LockStruct) PrintName() {
s.Mu.Lock()
defer s.Mu.Unlock()
s.setName("goroutine id : ")
s.setId(Goid())
fmt.Println(s.name, s.id)
}
func TestWithSingleGoroutine() {
fmt.Println("reentrant lock single goroutine test start")
ls := &LockStruct{Mu: NewReentrantLock()}
ls.PrintName()
fmt.Println("reentrant lock single goroutine test end")
}
func TestWithMultiGoroutine() {
fmt.Println("reentrant lock multi goroutine test start")
ls := &LockStruct{Mu: NewReentrantLock()}
for i := 0; i < 100; i++ {
go ls.PrintName()
}
time.Sleep(5 * time.Second)
fmt.Println("reentrant lock multi goroutine test end")
}
func main() {
TestWithSingleGoroutine()
TestWithMultiGoroutine()
}