go reentrant lock(可重入锁) 简单实现

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

  1. 多个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()
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值