zoukankan      html  css  js  c++  java
  • golang读写锁的实现及底层原理

    Golang的读写锁的实现

    结构体

    type RWMutex struct {
        w           Mutex  // held if there are pending writers
        writerSem   uint32 // 用于writer等待读完成排队的信号量
        readerSem   uint32 // 用于reader等待写完成排队的信号量
        readerCount int32  // 读锁的计数器
        readerWait  int32  // 等待读锁释放的数量
    }

    读写锁中允许加读锁的最大数量是4294967296,在go里面对写锁的计数采用了负值进行,通过递减最大允许加读锁的数量从而进行写锁对读锁的抢占

    const rwmutexMaxReaders = 1 << 30

    读锁加锁实现

    func (rw *RWMutex) RLock() {
        if race.Enabled {
            _ = rw.w.state
            race.Disable()
        }
        // 累加reader计数器,如果小于0则表明有writer正在等待
        if atomic.AddInt32(&rw.readerCount, 1) < 0 {
            // 当前有writer正在等待读锁,读锁就加入排队
            runtime_SemacquireMutex(&rw.readerSem, false)
        }
        if race.Enabled {
            race.Enable()
            race.Acquire(unsafe.Pointer(&rw.readerSem))
        }
    }

    读锁释放实现

    func (rw *RWMutex) RUnlock() {
        if race.Enabled {
            _ = rw.w.state
            race.ReleaseMerge(unsafe.Pointer(&rw.writerSem))
            race.Disable()
        }
        // 如果小于0,则表明当前有writer正在等待
        if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {
            if r+1 == 0 || r+1 == -rwmutexMaxReaders {
                race.Enable()
                throw("sync: RUnlock of unlocked RWMutex")
            }
            // 将等待reader的计数减1,证明当前是已经有一个读的,如果值==0,则进行唤醒等待的
            if atomic.AddInt32(&rw.readerWait, -1) == 0 {
                // The last reader unblocks the writer.
                runtime_Semrelease(&rw.writerSem, false)
            }
        }
        if race.Enabled {
            race.Enable()
        }
    }

    加写锁实现

    func (rw *RWMutex) Lock() {
        if race.Enabled {
            _ = rw.w.state
            race.Disable()
        }
        // 首先获取mutex锁,同时多个goroutine只有一个可以进入到下面的逻辑
        rw.w.Lock()
        // 对readerCounter进行进行抢占,通过递减rwmutexMaxReaders允许最大读的数量
        // 来实现写锁对读锁的抢占
        r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
        // 记录需要等待多少个reader完成,如果发现不为0,则表明当前有reader正在读取,当前goroutine
        // 需要进行排队等待
        if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {
            runtime_SemacquireMutex(&rw.writerSem, false)
        }
        if race.Enabled {
            race.Enable()
            race.Acquire(unsafe.Pointer(&rw.readerSem))
            race.Acquire(unsafe.Pointer(&rw.writerSem))
        }
    }

    释放写锁实现

    func (rw *RWMutex) Unlock() {
        if race.Enabled {
            _ = rw.w.state
            race.Release(unsafe.Pointer(&rw.readerSem))
            race.Disable()
        }
    
        // 将reader计数器复位,上面减去了一个rwmutexMaxReaders现在再重新加回去即可复位
        r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)
        if r >= rwmutexMaxReaders {
            race.Enable()
            throw("sync: Unlock of unlocked RWMutex")
        }
        // 唤醒所有的读锁
        for i := 0; i < int(r); i++ {
            runtime_Semrelease(&rw.readerSem, false)
        }
        // 释放mutex
        rw.w.Unlock()
        if race.Enabled {
            race.Enable()
        }
    }

    Golang读写锁底层原理

    在加读锁和写锁的工程中都使用atomic.AddInt32来进行递增,而该指令在底层是会通过LOCK来进行CPU总线加锁的,因此多个CPU同时执行readerCount其实只会有一个成功,从这上面看其实是写锁与读锁之间是相对公平的,谁先达到谁先被CPU调度执行,进行LOCK锁cache line成功,谁就加成功锁
     

    底层实现的CPU指令

    底层的2条指令,通过LOCK指令配合CPU的MESI协议,实现可见性和内存屏障,同时通过XADDL则用来保证原子性,从而解决可见性与原子性问题

    // atomic/asm_amd64.s TEXT runtime∕internal∕atomic·Xadd(SB)
        LOCK
        XADDL   AX, 0(BX)
    可见性与内存屏障、原子性, 其中可见性通常是指在cpu多级缓存下如何保证缓存的一致性,即在一个CPU上修改了了某个数据在其他的CPU上不会继续读取旧的数据,内存屏障通常是为了CPU为了提高流水线性能,而对指令进行重排序而来,而原子性则是指的执行某个操作的过程的不可分割


     
     
  • 相关阅读:
    深度剖析Reges.Match
    Python入门(一)
    SQL Server部分锁说明理解
    虚拟机Linux安装redis(一)
    transform matrix阅读后的理解
    小程序SKU规格选择
    React 学习记录(二)
    React 学习记录(一)
    在MVC里面使用Response.Redirect方法后记得返回EmptyResult——转载自PowerCoder
    Nodejs的安装随笔
  • 原文地址:https://www.cnblogs.com/peteremperor/p/14097633.html
Copyright © 2011-2022 走看看