欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

详解Golang互斥锁内部实现

程序员文章站 2022-03-20 16:18:39
go语言提供了一种开箱即用的共享资源的方式,互斥锁(sync.mutex), sync.mutex的零值表示一个没有被锁的,可以直接使用的,一个goroutine获得互斥锁...

go语言提供了一种开箱即用的共享资源的方式,互斥锁(sync.mutex), sync.mutex的零值表示一个没有被锁的,可以直接使用的,一个goroutine获得互斥锁后其他的goroutine只能等到这个gorutine释放该互斥锁,在mutex结构中只公开了两个函数,分别是lock和unlock,在使用互斥锁的时候非常简单,本文并不阐述使用。

在使用sync.mutex的时候千万不要做值拷贝,因为这样可能会导致锁失效。当我们打开我们的ide时候跳到我们的sync.mutex 代码中会发现它有如下的结构:

type mutex struct {
 state int32   //互斥锁上锁状态枚举值如下所示
 sema uint32  //信号量,向处于gwaitting的g发送信号
}

const (
 mutexlocked = 1 << iota // 1 互斥锁是锁定的
 mutexwoken       // 2 唤醒锁
 mutexwaitershift = iota // 2 统计阻塞在这个互斥锁上的goroutine数目需要移位的数值
)

上面的state值分别为 0(可用) 1(被锁) 2~31等待队列计数

下面是互斥锁的源码,这里会有四个比较重要的方法需要提前解释,分别是runtime_canspin,runtime_dospin,runtime_semacquiremutex,runtime_semrelease,

1、runtime_canspin:比较保守的自旋,golang中自旋锁并不会一直自旋下去,在runtime包中runtime_canspin方法做了一些限制, 传递过来的iter大等于4或者cpu核数小等于1,最大逻辑处理器大于1,至少有个本地的p队列,并且本地的p队列可运行g队列为空。

//go:linkname sync_runtime_canspin sync.runtime_canspin
func sync_runtime_canspin(i int) bool {
 if i >= active_spin || ncpu <= 1 || gomaxprocs <= int32(sched.npidle+sched.nmspinning)+1 {
 return false
 }
 if p := getg().m.p.ptr(); !runqempty(p) {
 return false
 }
 return true
}

2、 runtime_dospin:会调用procyield函数,该函数也是汇编语言实现。函数内部循环调用pause指令。pause指令什么都不做,但是会消耗cpu时间,在执行pause指令时,cpu不会对它做不必要的优化。

//go:linkname sync_runtime_dospin sync.runtime_dospin
func sync_runtime_dospin() {
 procyield(active_spin_cnt)
}

3、runtime_semacquiremutex:

//go:linkname sync_runtime_semacquiremutex sync.runtime_semacquiremutex
func sync_runtime_semacquiremutex(addr *uint32) {
 semacquire(addr, semablockprofile|semamutexprofile)
}

4、runtime_semrelease:

//go:linkname sync_runtime_semrelease sync.runtime_semrelease
func sync_runtime_semrelease(addr *uint32) {
 semrelease(addr)
}
mutex的lock函数定义如下

func (m *mutex) lock() {
    //先使用cas尝试获取锁
 if atomic.compareandswapint32(&m.state, 0, mutexlocked) {
        //这里是-race不需要管它
 if race.enabled {
  race.acquire(unsafe.pointer(m))
 }
        //成功获取返回
 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
   awoke = true
  }
                //进入自旋状态
  runtime_dospin()
  iter++
  continue
  }
            //没有获取到锁,当前g进入gwaitting状态
  new = old + 1<<mutexwaitershift
 }
 if awoke {
  if new&mutexwoken == 0 {
  throw("sync: inconsistent mutex state")
  }
            //清除标记
  new &^= mutexwoken
 }
        //更新状态
 if atomic.compareandswapint32(&m.state, old, new) {
  if old&mutexlocked == 0 {
  break
  }
             
            // 锁请求失败,进入休眠状态,等待信号唤醒后重新开始循环
  runtime_semacquiremutex(&m.sema)
  awoke = true
  iter = 0
 }
 }

 if race.enabled {
 race.acquire(unsafe.pointer(m))
 }
}
mutex的unlock函数定义如下

func (m *mutex) unlock() {
 if race.enabled {
 _ = m.state
 race.release(unsafe.pointer(m))
 }

 // 移除标记
 new := atomic.addint32(&m.state, -mutexlocked)
 if (new+mutexlocked)&mutexlocked == 0 {
 throw("sync: unlock of unlocked mutex")
 }

 old := new
 for {
 //当休眠队列内的等待计数为0或者自旋状态计数器为0,退出
 if old>>mutexwaitershift == 0 || old&(mutexlocked|mutexwoken) != 0 {
  return
 }
 // 减少等待次数,添加清除标记
 new = (old - 1<<mutexwaitershift) | mutexwoken
 if atomic.compareandswapint32(&m.state, old, new) {
            // 释放锁,发送释放信号
  runtime_semrelease(&m.sema)
  return
 }
 old = m.state
 }
}

互斥锁无冲突是最简单的情况了,有冲突时,首先进行自旋,,因为大多数的mutex保护的代码段都很短,经过短暂的自旋就可以获得;如果自旋等待无果,就只好通过信号量来让当前goroutine进入gwaitting状态。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。