GO 读写锁sync.RWMutex(2)

读写锁首先是内置了一个互斥锁,然后再加上维护各种计数器来实现的读写锁,紧接着提供了四个函数支撑着读写锁操作,由 Lock 和Unlock 分别支持写锁的锁定和释放,由RLock 和RUnlock 来支持读锁的的锁定和释放。读锁不涉及 内置mutex的使用,写锁用了mutex来排斥其他写锁.

读写互斥锁的实现比较有技巧性

  1. 读锁不能阻塞读锁,引入readerCount实现
  2. 读锁需要阻塞写锁,直到所以读锁都释放,引入readerSem实现
  3. 写锁需要阻塞读锁,直到所以写锁都释放,引入wirterSem实现
  4. 写锁需要阻塞写锁,引入Metux实现
type RWMutex struct {
    w           Mutex  // 互斥锁
    writerSem   uint32 // 写锁信号量
    readerSem   uint32 // 读锁信号量
    readerCount int32  // 读锁计数器
    readerWait  int32  // 获取写锁时需要等待的读锁释放数量
}
 
const rwmutexMaxReaders = 1 << 30    // 支持最多2^30个读锁
 
// 读锁锁定:
//
// 它不应该用于递归读锁定;
func (rw *RWMutex) RLock() {
 
    // 竞态检测
    if race.Enabled {
        _ = rw.w.state
        race.Disable()
    }
 
    // 每次goroutine获取读锁时,readerCount+1
    // 如果写锁已经被获取,那么readerCount在 - rwmutexMaxReaders与 0 之间,这时挂起获取读锁的goroutine,
    // 如果写锁没有被获取,那么readerCount>=0,获取读锁,不阻塞
    // 通过readerCount的正负判断读锁与写锁互斥,如果有写锁存在就挂起读锁的goroutine,多个读锁可以并行
 
    if atomic.AddInt32(&rw.readerCount, 1) < 0 {
        // 将goroutine排到G队列的后面,挂起goroutine, 监听readerSem信号量
        runtime_Semacquire(&rw.readerSem)
    }
 
    // 竞态检测
    if race.Enabled {
        race.Enable()
        race.Acquire(unsafe.Pointer(&rw.readerSem))
    }
}
 
// 释放读锁
 
// 读锁不会影响其他读操作
// 如果在进入RUnlock时没有锁没有被施加读锁的话,则会出现运行时错误。
func (rw *RWMutex) RUnlock() {
 
    // 竞态检测
    if race.Enabled {
        _ = rw.w.state
        race.ReleaseMerge(unsafe.Pointer(&rw.writerSem))
        race.Disable()
    }
    // 读锁计数器 -1
    // 有四种情况,其中后面三种都会进这个 if
    // 【一】有读锁,单没有写锁被挂起
    // 【二】有读锁,且也有写锁被挂起
    // 【三】没有读锁且没有写锁被挂起的时候, r+1 == 0
    // 【四】没有读锁但是有写锁被挂起,则 r+1 == -(1 << 30)
    if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {
        // 读锁早就被没有了,那么在此 -1 是需要抛异常的
        // 这里只有当读锁没有的时候才会出现的两种极端情况
        // 【一】没有读锁且没有写锁被挂起的时候, r+1 == 0
        // 【二】没有读锁但是有写锁被挂起,则 r+1 == -(1 << 30)
        if r+1 == 0 || r+1 == -rwmutexMaxReaders {
            race.Enable()
            throw("sync: RUnlock of unlocked RWMutex")
        }
        // 否则,就属于 有读锁,且也有写锁被挂起
        // 如果获取写锁时的goroutine被阻塞,这时需要获取读锁的goroutine全部都释放,才会被唤醒
        // 更新需要释放的 写锁的等待读锁释放数目
        // 最后一个读锁解除时,写锁的阻塞才会被解除.
        if atomic.AddInt32(&rw.readerWait, -1) == 0 {
            // 更新信号量,通知被挂起的写锁去获取锁
            runtime_Semrelease(&rw.writerSem, false)
        }
    }
    if race.Enabled {
        race.Enable()
    }
}
 
// 对一个已经lock的rw上锁会被阻塞
// 如果锁已经锁定以进行读取或写入,则锁定将被阻塞,直到锁定可用。
func (rw *RWMutex) Lock() {
    if race.Enabled {
        _ = rw.w.state
        race.Disable()
    }
    // 先获取一把互斥锁
    // 首先,获取互斥锁,与其他来获取写锁的goroutine 互斥
    rw.w.Lock()
    // 告诉其他来获取读锁操作的goroutine,现在有人获取了写锁
    // 减去最大的读锁数量,用0 -负数 来表示写锁已经被获取
    r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
 
    // 设置需要等待释放的读锁数量,如果有,则挂起获取 竞争写锁 goroutine
    if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {
        // 挂起,监控写锁信号量
        runtime_Semacquire(&rw.writerSem)
    }
    if race.Enabled {
        race.Enable()
        race.Acquire(unsafe.Pointer(&rw.readerSem))
        race.Acquire(unsafe.Pointer(&rw.writerSem))
    }
}
 
// Unlock 已经Unlock的锁会被阻塞.
// 如果在写锁时,rw没有被解锁,则会出现运行时错误。
//
// 与互斥锁一样,锁定的RWMutex与特定的goroutine无关。
// 一个goroutine可以RLock(锁定)RWMutex然后安排另一个goroutine到RUnlock(解锁)它。
func (rw *RWMutex) Unlock() {
    if race.Enabled {
        _ = rw.w.state
        race.Release(unsafe.Pointer(&rw.readerSem))
        race.Release(unsafe.Pointer(&rw.writerSem))
        race.Disable()
    }
 
    // 向 读锁的goroutine发出通知,现在已经没有写锁了
    // 还原加锁时减去的那一部分readerCount
    r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)
    // 读锁数目超过了 最大允许数
    if r >= rwmutexMaxReaders {
        race.Enable()
        throw("sync: Unlock of unlocked RWMutex")
    }
    // 唤醒获取读锁期间所有被阻塞的goroutine
    for i := 0; i < int(r); i++ {
        runtime_Semrelease(&rw.readerSem, false)
    }
    // 释放互斥锁资源
    rw.w.Unlock()
    if race.Enabled {
        race.Enable()
    }
}
 
 
// RLocker返回一个Locker接口的实现
// 通过调用rw.RLock和rw.RUnlock来锁定和解锁方法。
func (rw *RWMutex) RLocker() Locker {
    return (*rlocker)(rw)
}
 
type rlocker RWMutex
 
func (r *rlocker) Lock()   { (*RWMutex)(r).RLock() }
func (r *rlocker) Unlock() { (*RWMutex)(r).RUnlock() }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,657评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,889评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,057评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,509评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,562评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,443评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,251评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,129评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,561评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,779评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,902评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,621评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,220评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,838评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,971评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,025评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,843评论 2 354

推荐阅读更多精彩内容