Golang源码4-互斥锁与读写锁

86 阅读4分钟

一、sync.Mutex

1.1 锁升级

  • 乐观锁: 即自旋+cas

  • 悲观锁:阻塞当前goroutine等待被唤醒。

1.2 Mutex模式

type Mutex struct {
// 锁状态, int32 的bitmap, 最后一个bit位表示是否上锁,还有一些其他的标志位标识其他信息。
 state int32
 // 信号量
 sema  uint32
}

1.2.1 Normal 正常模式

Normal为mutex互斥锁的默认模式,当一个goroutine尝试获取锁时,如果锁处于加锁状态,该goroutine将尝试以自旋的方式抢锁,当锁被释放时,该goroutine将与阻塞队列被唤醒的头部goroutine进行抢锁,若成功抢锁,被唤醒的goroutine会被重新放回阻塞队列头部,等待下次唤醒;若经过一定次数的自旋后仍不得锁,将会被加入阻塞队列。

当阻塞队列存在goroutine抢锁达到1ms仍抢不到锁时,会进入饥饿模式

1.2.2 Straving 饥饿模式

饥饿模式下,新到来的goroutine不会进行自旋抢锁,而是被直接放入阻塞队列末尾,等待被唤醒。

当阻塞队列为空,或阻塞队列抢锁的goroutine等待时间小于1ms时,回到正常模式

1.3 Lock() 加锁

const (
 mutexLocked = 1 << iota         //   1
 mutexWoken  = 2                 //  10
 mutexStarving = 4               // 100
 mutexWaiterShift = iota = 3     //  11
 )

func (m *Mutex) Lock() {
 // CAS
 if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
      if race.Enabled {
           race.Acquire(unsafe.Pointer(m))
      }
      return
 }
 m.lockSlow()
}


func (m *Mutex) lockSlow() {
 var waitStartTime int64     
 starving := false
 awoke := false
 iter := 0
 old := m.state
 // 循环尝试加锁
 for {
     // 当前已上锁 且 处于正常模式, canSpin判断是否可以自旋
     if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {
         // Active spinning makes sense.
         // Try to set mutexWoken flag to inform Unlock
         // to not wake other blocked goroutines.
         // old & mutexWoken == 0 说明 无g在抢锁
         // old>>mutexWaiterShift != 0 即阻塞队列不为空
         // 有阻塞队列,自己又不是被唤醒的,此时尝试吧mutexWoken置为1 标识已有g在抢锁
         if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&
             atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {
             awoke = true
         }
         // 执行自旋
         runtime_doSpin()
         // 自旋次数+1
         iter++
         //  m.state 可能其他g改变之后,由加锁状态->解锁, 那么就无法进入当前if分支,即开始尝试加锁
         old = m.state
         continue
      }
      
      // 自旋完,接下来要么加锁成功,要么没有成功需要阻塞挂起
  
      // new期望的新值, old变为new 在后续可能成功也可能失败
      new := old
      // Don't try to acquire starving mutex, new arriving goroutines must queue.
      // 非饥饿模式,尝试加锁, 否则饥饿模式其实放弃了这一轮加锁
      if old&mutexStarving == 0 {
           // 尝试加锁,随后可能成功也可能失败。
           new |= mutexLocked
      }
      // 老的锁要么是加锁, 要么处于饥饿状态, 此时必定抢锁失败,需要阻塞挂起
      if old&(mutexLocked|mutexStarving) != 0 {
           // mutexWaiterShift =3 需要将阻塞队列数量递增,加上即可
           new += 1 << mutexWaiterShift
      }
      // The current goroutine switches mutex to starvation mode.
      // But if the mutex is currently unlocked, don't do the switch.
      // Unlock expects that starving mutex has waiters, which will not
      // be true in this case.
      // starving 是否饥饿模式可能再后续流程中被修改
      // 如果自旋过程中starving变成饥饿模式了, 即需要将new改为饥饿状态
      if starving && old&mutexLocked != 0 {
           new |= mutexStarving
      }
  
      // 如果是被唤醒的g,那么要么抢锁成功,要么抢锁失败回到阻塞队列
      // 无论那种情况都需要将mutexWoken 置为0(new &^= mutexWoken) 标识无g在抢锁
      if awoke {
           // The goroutine has been woken from sleep,
           // so we need to reset the flag in either case.
           if new&mutexWoken == 0 {
                throw("sync: inconsistent mutex state")
           }
           new &^= mutexWoken   // &^ 这个操作其实吧mutexWoken 变为0
      }
      // CAS 尝试old 改 new
      if atomic.CompareAndSwapInt32(&m.state, old, new) {
           // 正常模式且没有加锁  此时加锁成功 跳出
           if old&(mutexLocked|mutexStarving) == 0 {
                break // locked the mutex with CAS
           }
           // If we were already waiting before, queue at the front of the queue.
           // 等待时间
           queueLifo := waitStartTime != 0
           if waitStartTime == 0 {
                waitStartTime = runtime_nanotime()
           }
           // 挂起
           runtime_SemacquireMutex(&m.sema, queueLifo, 1)
           // 从队列中被唤醒,判断等待时长来确认是否需要变为饥饿模式
           starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNs
           old = m.state
           // 如果是饥饿模式,自己又是从队列中被唤起的,此时肯定可以获取到锁
           if old&mutexStarving != 0 {
                // If this goroutine was woken and mutex is in starvation mode,
                // ownership was handed off to us but mutex is in somewhat
                // inconsistent state: mutexLocked is not set and we are still
                // accounted as waiter. Fix that.
                if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 {
                     throw("sync: inconsistent mutex state")
                }
                delta := int32(mutexLocked - 1<<mutexWaiterShift)
                if !starving || old>>mutexWaiterShift == 1 {
                     // Exit starvation mode.
                     // Critical to do it here and consider wait time.
                     // Starvation mode is so inefficient, that two goroutines
                     // can go lock-step infinitely once they switch mutex
                     // to starvation mode.
                     // 
                     delta -= mutexStarving
                }
                atomic.AddInt32(&m.state, delta)
                // 加锁跳出
                break
           }
           awoke = true
           iter = 0
      } else {
           // 说明其他g改了state, 这时old变state
           old = m.state
      }
 }

 if race.Enabled {
      race.Acquire(unsafe.Pointer(m))
 }
}
  1. 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
}
  1. runtime_doSpin 自旋,底层执行汇编PAUSE,持续消耗cpu时间
func sync_runtime_doSpin() {
    procyield(active_spin_cnt)
}

1.4 UnLock() 解锁

func (m *Mutex) Unlock() {
 if race.Enabled {
  _ = m.state
  race.Release(unsafe.Pointer(m))
 }

 // Fast path: drop lock bit.
 new := atomic.AddInt32(&m.state, -mutexLocked)
 // 解锁成功直接返回,没有进入unlockSlow(new)
 if new != 0 {
  // Outlined slow path to allow inlining the fast path.
  // To hide unlockSlow during tracing we skip one extra frame when tracing GoUnblock.
 // 解锁后看看是否需要唤醒阻塞队列
  m.unlockSlow(new)
 }
}

func (m *Mutex) unlockSlow(new int32) {
 // 先看是不是本身没有被锁,是则panic
 if (new+mutexLocked)&mutexLocked == 0 {
  fatal("sync: unlock of unlocked mutex")
 }
 // 正常模式
 if new&mutexStarving == 0 {
  old := new
  for {
   // If there are no waiters or a goroutine has already
   // been woken or grabbed the lock, no need to wake anyone.
   // In starvation mode ownership is directly handed off from unlocking
   // goroutine to the next waiter. We are not part of this chain,
   // since we did not observe mutexStarving when we unlocked the mutex above.
   // So get off the way.
   // 阻塞队列是否为空,为空直接返回, 
   // 如果mutexLocked、mutexWoken、mutexStarving有一个不为0
   // mutexlocked 为1表示又被加锁了,不用处理后续,
   // mutexWoken  为1表示又g在抢锁,不用后续处理
   // mutexStarving  如果饥饿模式,不用后续处理
   if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken|mutexStarving) != 0 {
    return
   }
   // 阻塞队列数量-1,并记录为有g在抢锁,即自己在抢
   new = (old - 1<<mutexWaiterShift) | mutexWoken
   // cas 尝试修改
   if atomic.CompareAndSwapInt32(&m.state, old, new) {
    // 修改成功 唤醒队列头部即可
    runtime_Semrelease(&m.sema, false, 1)
    return
   }
   // 其他g改了,则开启下一轮循环
   old = m.state
  }
 } else {
  // Starving mode: handoff mutex ownership to the next waiter, and yield
  // our time slice so that the next waiter can start to run immediately.
  // Note: mutexLocked is not set, the waiter will set it after wakeup.
  // But mutex is still considered locked if mutexStarving is set,
  // so new coming goroutines won't acquire it.
  // 饥饿模式,唤醒下个阻塞队列里的g即可
  runtime_Semrelease(&m.sema, true, 1)
 }
}

二、sync.RWMutex

读写锁适用于读多写少的场景

1.1 RWMutex

type RWMutex struct {
 w           Mutex  // held if there are pending writers
 // 写阻塞队列的信号量
 writerSem   uint32 // semaphore for writers to wait for completing readers
 // 读阻塞队列的信号量
 readerSem   uint32 // semaphore for readers to wait for completing writers
 // 等待或占用读锁的g的数量
 readerCount int32  // number of pending readers
 // 占用读锁的g的数量
 readerWait  int32  // number of departing readers
}

1.2 RLock 读加锁

func (rw *RWMutex) RLock() {
 if race.Enabled {
  _ = rw.w.state
  race.Disable()
 }
 // 正常情况下readerCount都是+1都是大于0, 只有在之前有写锁出现时,readerCount 会被减 (1<<30)
 if atomic.AddInt32(&rw.readerCount, 1) < 0 {
  // A writer is pending, wait for it.
  // 读挂起
  runtime_SemacquireMutex(&rw.readerSem, false, 0)
 }
 if race.Enabled {
  race.Enable()
  race.Acquire(unsafe.Pointer(&rw.readerSem))
 }
}

1.3 RUnlock 读解锁

// on entry to RUnlock.
func (rw *RWMutex) RUnlock() {
 if race.Enabled {
  _ = rw.w.state
  race.ReleaseMerge(unsafe.Pointer(&rw.writerSem))
  race.Disable()
 }
 
 // readerCount-1 < 0 说明之前有写锁
 if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {
  // Outlined slow-path to allow the fast-path to be inlined
  rw.rUnlockSlow(r)
 }
 if race.Enabled {
  race.Enable()
 }
}


func (rw *RWMutex) rUnlockSlow(r int32) {
 // 校验
 if r+1 == 0 || r+1 == -rwmutexMaxReaders {
  race.Enable()
  fatal("sync: RUnlock of unlocked RWMutex")
 }
 // readerWait -1 == 0 表示 说明当前是最后一个释放读锁的,需要唤醒写阻塞队列
 if atomic.AddInt32(&rw.readerWait, -1) == 0 {
  // The last reader unblocks the writer.
  runtime_Semrelease(&rw.writerSem, false, 1)
 }
}

1.4 Lock 写锁

const rwmutexMaxReaders = 1 << 30
func (rw *RWMutex) Lock() {
 if race.Enabled {
  _ = rw.w.state
  race.Disable()
 }
 // First, resolve competition with other writers.
 // 加互斥锁, 即使加锁成功,也要看之前是否有读锁
 rw.w.Lock()
 // Announce to readers there is a pending writer.
 // 原子操作- 再+, 这个时候r 如果不为0 即肯定有读锁
 r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
 // Wait for active readers.
 // 需要等待的读的g的数量加到readerWait里
 if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {
  // 阻塞挂起
  runtime_SemacquireMutex(&rw.writerSem, false, 0)
 }
 if race.Enabled {
  race.Enable()
  race.Acquire(unsafe.Pointer(&rw.readerSem))
  race.Acquire(unsafe.Pointer(&rw.writerSem))
 }
}

1.5 Lock 解写锁

func (rw *RWMutex) Unlock() {
 if race.Enabled {
  _ = rw.w.state
  race.Release(unsafe.Pointer(&rw.readerSem))
  race.Disable()
 }

 // Announce to readers there is no active writer.
 // readerConnt + rwmutexMaxReaders >= rwmutexMaxReaders 要么没有加过写锁,要么读g超过数量 
 r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)
 if r >= rwmutexMaxReaders {
  race.Enable()
  fatal("sync: Unlock of unlocked RWMutex")
 }
 //  唤醒所有的读,优先释放读,读会先去竞争读锁
 for i := 0; i < int(r); i++ {
  runtime_Semrelease(&rw.readerSem, false, 0)
 }
 
 // 释放互斥锁,再释放写锁,其他写锁的g 再去竞争写锁
 rw.w.Unlock()
 if race.Enabled {
  race.Enable()
 }
}