• 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数
    )

    关键函数设计:

    lock函数:

    //获取锁,如果锁已经在使用,则会阻塞一直到锁可用
    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函数,终于可以来点轻松的了

    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. 当前goroutine进入自旋锁逻辑等待中

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

    二、锁数据结构设计十分精简

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

  • 相关阅读:
    冲刺第一天
    Nacos微服务体系配置管理
    Nacos配置中心客户端程序
    Nacos配置中心最佳实践
    SpringCloud2020整合Nacos-Bootstrap配置不生效的解决
    18.SpringCloud Alibaba Nacos服务注册和配置中心
    17、SpringCloud Alibaba入门简介
    SpringBoot热部署
    SpringBoot请求处理-常用参数注解使用
    5、创建springcloud alibaba工程
  • 原文地址:https://www.cnblogs.com/zongjiang/p/6577297.html
Copyright © 2020-2023  润新知