sync.Mutex 源码学习

Posted by Jason on Tuesday, October 22, 2019

TOC

sync.mutex

sync.Mutex 源码阅读有两篇写的比较好的文章,就不逐一详细说明代码实现了。在本文中根据自己阅读源码时想到的几个问题,从源码中找出答案,如有错误希望能指正。

怎么保证公平性;

mutex 分为两种模式:普通(normal)、饥饿(starvation)。

  • 普通模式下,协程会先进行若干次自旋,满足某些条件(可见sync_runtime_canSpin函数)则获取锁/进入阻塞等待状态。如果锁被释放,一个协程进入阻塞状态,同时另一个新的协程试图获取锁,由于新协程在CPU执行过程,会有较高概率获取锁(if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 && atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) { awoke = true })。所以,如果不做处理会使阻塞的协程长时间获取不到锁。

  • 饥饿模式下,直接将锁的所有权交给队列的第一个(runtime_Semrelease(&m.sema, true, 1))

func (m *Mutex) Lock() {
......
    //饥饿状态直接获取锁
    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
    }
.......
  • 保证公平性,如果一个协程被唤醒后没有获取锁,则被加入到队列的头部
......
    queueLifo := waitStartTime != 0
    if waitStartTime == 0 {
        waitStartTime = runtime_nanotime()
    }
    runtime_SemacquireMutex(&m.sema, queueLifo, 1)
.......

woken 标记作用是什么?没有是不是可以?

woken 标记功能:在非饥饿状态下,用来标记已经唤醒过某个等待的协程。并且这个状态只能由被唤醒(runtime_Semrelease)的协程清除;如果 woken 状态已经存在,解锁直接返回。

解锁阶段


func (m *Mutex) Unlock() {
....
    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.
            // 没有等待获取锁的协程、有协程已经被唤醒、有协程获取锁、有饥饿的协程条件下,直接返回。不会试图去唤醒等待锁的协程
            if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken|mutexStarving) != 0 {
                return
            }
            // Grab the right to wake someone.
            new = (old - 1<<mutexWaiterShift) | mutexWoken
            if atomic.CompareAndSwapInt32(&m.state, old, new) {
                runtime_Semrelease(&m.sema, false, 1)
                return
            }
            old = m.state
        }
.....

加锁阶段,


func (m *Mutex) Lock() {
....
    for{
        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.
            // 试图抢占锁,标记 woken 状态,阻止解锁时通过信号通知其他阻塞的协程
            if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&
                atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {
                awoke = true
            }
            runtime_doSpin()
            iter++
            old = m.state
            continue
        }

mutex 的性能问题是什么?

简单粗暴

参考

  1. sync.mutex 源代码分析
  2. 源码剖析golang中sync.Mutex

「真诚赞赏,手留余香」

Jason Blog

真诚赞赏,手留余香

使用微信扫描二维码完成支付


comments powered by Disqus