当前位置: 移动技术网 > IT编程>脚本编程>Go语言 > 详解Golang互斥锁内部实现

详解Golang互斥锁内部实现

2017年12月08日  | 移动技术网IT编程  | 我要评论

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状态。

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

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网