万字图解| 深入揭秘Golang锁结构:Mutex(下)

大家好,我是「云舒编程」,今天我们来聊聊Golang锁结构:Mutex。

文章首发于微信公众号:云舒编程

关注公众号获取:
1、大厂项目分享
2、各种技术原理分享
3、部门内推

一、前言

书接上回,在万字图解| 深入揭秘Golang锁结构:Mutex(上)一文中,我们已经研究了Golang mutex V1和V2版本的实现。接下来我们继续研究V3和V4版本的实现。

二、面试中遇到Mutex

为了让剧情顺利发展,我们依旧使用万字图解| 深入揭秘Golang锁结构:Mutex(上)一文中的面试对话模式。
面试官:你现在实现的锁的确给了新来的Goroutine直接获取锁的机会,但是还不够优雅。比如说,新Goroutine尝试获取锁失败的那一刻,锁就被释放了,但是新Goroutine需要等到下一次信号量唤醒加调度才有机会再次获取锁,这样其实浪费了新Goroutine的CPU时间,你可以再优化下吗?
:考虑到这种情况,可以尝试给新的Goroutine多次获取锁的机会,说白了就是允许自旋,但是需要给自旋加一些限制条件,避免最开始提到的性能问题。
面试官:需要加哪些限制条件呢?
:首先需要限制自旋的次数,其次操作系统的处理器个数和Golang 调度的P个数都必须大于1,否则就会是串行,自旋就没有意义了。
面试官:不错,怎么实现呢?
:我来写下:

const (
    mutexLocked      = 1 // mutex is locked
    mutexWoken       = 2 
    mutexWaiterShift = 2
)

type Mutex struct {
   
   
    state int32
    sema  uint32
}

func (m *Mutex) Lock() {
   
   
    //给新来的协程直接加锁的机会
    if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
   
   
        return
    }

    //上面没有加锁成功,尝试在接下来的唤醒中去竞争锁
    awoke := false //表示当前协程是不是被唤醒的
    iter := 0 //记录当前自旋的次数
    for {
   
   
        old := m.state
        new := old | mutexLocked // 设置锁标志位为1
        if old&mutexLocked != 0 {
   
   
            //判断是否满足自旋条件
            if runtime_canSpin(iter) {
   
   
    if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&
     atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {
   
   
     awoke = true
    }

                //内部调用procyield函数,该函数也是汇编语言实现。
                //函数内部循环调用PAUSE指令。减少cpu的消耗,节省电量。
                //指令的本质功能:让加锁失败时cpu睡眠30个(about)clock,从而使得读操作的频率低很多。流水线重排的代价也会小很多。
    runtime_doSpin() 
    iter++
    continue
   }
            
            new = old + 1<<mutexWaiterShift //锁没有释放,当前协程可能会阻塞在信号量上,先将waiter+1
        }
       ··· //剩下的不变
    }
}
//判断是否可以自旋,同时满足以下4个条件才能自旋:
//1、自旋次数小于4次
//2、
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值