golang mutex互斥锁分析

互斥锁:没有读锁写锁之分,同一时刻,只能有一个gorutine获取一把锁数据结构

数据结构设计:函数

type Mutex struct {
  state int32 // 将一个32位整数拆分为 当前阻塞的goroutine数(30位)|唤醒状态(1位)|锁状态(1位) 的形式,来简化字段设计
  sema uint32 // 信号量
}

const (
  mutexLocked = 1 << iota // 0001 含义:用最后一位表示当前对象锁的状态,0-未锁住 1-已锁住
  mutexWoken // 0010 含义:用倒数第二位表示当前对象是否被唤醒 0-唤醒 1-未唤醒
  mutexWaiterShift = iota // 2 含义:从倒数第二位往前的bit位表示在排队等待的goroutine数
)

关键函数设计:ui

lock函数:atom

//获取锁,若是锁已经在使用,则会阻塞一直到锁可用
func (m *Mutex) Lock() {
    // m.sate == 0时说明当前对象尚未被锁住过,进行原子操赋值做操做设置mutexLocked状态,CompareAnSwapInt32返回true
    // m.sate != 1时恰好相反说明对象已被其余goroutine锁住,不会进行原子赋值操做设置,CopareAndSwapInt32返回false
    if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) { 
        if race.Enabled {
            race.Acquire(unsafe.Pointer(m))
        }
        return
    }

    awoke := false
    iter := 0
    for {
        old := m.state // 保存对象当前锁状态
        new := old | mutexLocked // 保存对象即将被设置成的状态
        if old&mutexLocked != 0 { // 判断对象是否处于锁定状态
      if runtime_canSpin(iter) { // 判断当前goroutine是否能够进入自旋锁
                if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&
                    atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {
                    awoke = true
                }
                runtime_doSpin() // 进入自旋锁后当前goroutine并不挂起,仍然在占用cpu资源,因此重试必定次数后,不会再进入自旋锁逻辑
                iter++
                continue
          }
          // 更新阻塞goroutine的数量
          new = old + 1<<mutexWaiterShift //new = 2
        }
        if awoke {
            if new&mutexWoken == 0 {
                panic("sync: inconsistent mutex state")
            }
       // 设置唤醒状态位0
            new &^= mutexWoken
        }
        if atomic.CompareAndSwapInt32(&m.state, old, new) {
            if old&mutexLocked == 0 { // 若是对象本来不是锁定状态,对象已经获取到了锁
                break
            }
            // 若是对象本来就是锁定状态,挂起当前goroutine,进入休眠等待状态
            runtime_Semacquire(&m.sema)
            awoke = true
            iter = 0
        }
    }

    if race.Enabled {
        race.Acquire(unsafe.Pointer(m))
    }
}

再来看看unlock函数,终于能够来点轻松的了spa

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 { // 若是原来锁不是锁定状态,报错
        panic("sync: unlock of unlocked mutex")
    }

    old := new
    for {
        // 1. 若是没有阻塞的goroutine直接返回
        // 2. 若是已经被其余goroutine获取到锁了直接返回
// 须要说明的是为何是old&(mutexLocked|mutexWoken)而不是old&mutexLocked
// 首先若是是old&mutexLocked的话,那锁就无法释放了
// 最主要的一点是lock时进入自旋锁逻辑后,goroutine持有的对象状态会设置为mutexLocked|mutexWoken
// 这种状况让再去解锁后mutexWaiterShift数就会出现不一致状况
if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken) != 0 { return } // 更新阻塞的goroutine个数 new = (old - 1<<mutexWaiterShift) | mutexWoken if atomic.CompareAndSwapInt32(&m.state, old, new) { // 通知阻塞的goroutine runtime_Semrelease(&m.sema) return } old = m.state } }

 

总结:设计

1、互斥效果实现方式code

  1. 当前goroutine进入自旋锁逻辑等待中对象

  2. 挂起当前goroutine等待其余goroutine解锁通知,经过信号量实现blog

2、锁数据结构设计十分精简资源

     goroutine数(30位)|唤醒状态(1位)|锁状态(1位)

相关文章
相关标签/搜索