• golang 基础-golang里面的读写锁实现与核心原理分析


    前言

    golang面试中,我们经常会被问到golang里面的读写锁的原理,golang里的读写锁解决了什么问题,golang读写锁的底层原理是什么?下面我们一起学习了解一下golang里面的读写锁实现与核心原理


    一、golang里的读写锁是什么?

    golang读写锁即是针对于读写操作的互斥锁。它与普通的互斥锁最大的不同就是,它可以分别针对读操作和写操作进行锁定和解锁操作。
    golang读写锁遵循的访问控制规则与互斥锁有所不同。在读写锁管辖的范围内,它允许任意个读操作的同时进行。但是,在同一时刻,它只允许有一个写操作在进行。并且,在某一个写操作被进行的过程中,读操作的进行也是不被允许的。也就是说,读写锁控制下的多个写操作之间都是互斥的,并且写操作与读操作之间也都是互斥的。但是,多个读操作之间却不存在互斥关系。
    这样的规则对于针对同一块数据的并发读写来讲是非常贴切的。因为,无论读操作的并发量有多少,这些操作都不会对数据本身造成变更。而写操作不但会对同时进行的其他写操作进行干扰,还有可能造成同时进行的读操作的结果的不正确。
    在Go语言中,读写锁由结构体类型sync.RWMutex代表。与互斥锁类似,sync.RWMutex类型的零值就已经是立即可用的读写锁了

    二、golang读写锁rwmutex.go的源码:

    // Copyright 2009 The Go Authors. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.
    
    package sync
    
    import (
    	"internal/race"
    	"sync/atomic"
    	"unsafe"
    )
    
    // There is a modified copy of this file in runtime/rwmutex.go.
    // If you make any changes here, see if you should make them there.
    
    // A RWMutex is a reader/writer mutual exclusion lock.
    // The lock can be held by an arbitrary number of readers or a single writer.
    // The zero value for a RWMutex is an unlocked mutex.
    //
    // A RWMutex must not be copied after first use.
    //
    // If a goroutine holds a RWMutex for reading and another goroutine might
    // call Lock, no goroutine should expect to be able to acquire a read lock
    // until the initial read lock is released. In particular, this prohibits
    // recursive read locking. This is to ensure that the lock eventually becomes
    // available; a blocked Lock call excludes new readers from acquiring the
    // lock.
    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
    	readerCount int32  // number of pending readers
    	readerWait  int32  // number of departing readers
    }
    
    const rwmutexMaxReaders = 1 << 30
    
    // Happens-before relationships are indicated to the race detector via:
    // - Unlock  -> Lock:  readerSem
    // - Unlock  -> RLock: readerSem
    // - RUnlock -> Lock:  writerSem
    //
    // The methods below temporarily disable handling of race synchronization
    // events in order to provide the more precise model above to the race
    // detector.
    //
    // For example, atomic.AddInt32 in RLock should not appear to provide
    // acquire-release semantics, which would incorrectly synchronize racing
    // readers, thus potentially missing races.
    
    // RLock locks rw for reading.
    //
    // It should not be used for recursive read locking; a blocked Lock
    // call excludes new readers from acquiring the lock. See the
    // documentation on the RWMutex type.
    func (rw *RWMutex) RLock() {
    	if race.Enabled {
    		_ = rw.w.state
    		race.Disable()
    	}
    	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))
    	}
    }
    
    // TryRLock tries to lock rw for reading and reports whether it succeeded.
    //
    // Note that while correct uses of TryRLock do exist, they are rare,
    // and use of TryRLock is often a sign of a deeper problem
    // in a particular use of mutexes.
    func (rw *RWMutex) TryRLock() bool {
    	if race.Enabled {
    		_ = rw.w.state
    		race.Disable()
    	}
    	for {
    		c := atomic.LoadInt32(&rw.readerCount)
    		if c < 0 {
    			if race.Enabled {
    				race.Enable()
    			}
    			return false
    		}
    		if atomic.CompareAndSwapInt32(&rw.readerCount, c, c+1) {
    			if race.Enabled {
    				race.Enable()
    				race.Acquire(unsafe.Pointer(&rw.readerSem))
    			}
    			return true
    		}
    	}
    }
    
    // RUnlock undoes a single RLock call;
    // it does not affect other simultaneous readers.
    // It is a run-time error if rw is not locked for reading
    // on entry to RUnlock.
    func (rw *RWMutex) RUnlock() {
    	if race.Enabled {
    		_ = rw.w.state
    		race.ReleaseMerge(unsafe.Pointer(&rw.writerSem))
    		race.Disable()
    	}
    	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()
    		throw("sync: RUnlock of unlocked RWMutex")
    	}
    	// A writer is pending.
    	if atomic.AddInt32(&rw.readerWait, -1) == 0 {
    		// The last reader unblocks the writer.
    		runtime_Semrelease(&rw.writerSem, false, 1)
    	}
    }
    
    // Lock locks rw for writing.
    // If the lock is already locked for reading or writing,
    // Lock blocks until the lock is available.
    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 := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
    	// Wait for active readers.
    	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))
    	}
    }
    
    // TryLock tries to lock rw for writing and reports whether it succeeded.
    //
    // Note that while correct uses of TryLock do exist, they are rare,
    // and use of TryLock is often a sign of a deeper problem
    // in a particular use of mutexes.
    func (rw *RWMutex) TryLock() bool {
    	if race.Enabled {
    		_ = rw.w.state
    		race.Disable()
    	}
    	if !rw.w.TryLock() {
    		if race.Enabled {
    			race.Enable()
    		}
    		return false
    	}
    	if !atomic.CompareAndSwapInt32(&rw.readerCount, 0, -rwmutexMaxReaders) {
    		rw.w.Unlock()
    		if race.Enabled {
    			race.Enable()
    		}
    		return false
    	}
    	if race.Enabled {
    		race.Enable()
    		race.Acquire(unsafe.Pointer(&rw.readerSem))
    		race.Acquire(unsafe.Pointer(&rw.writerSem))
    	}
    	return true
    }
    
    // Unlock unlocks rw for writing. It is a run-time error if rw is
    // not locked for writing on entry to Unlock.
    //
    // As with Mutexes, a locked RWMutex is not associated with a particular
    // goroutine. One goroutine may RLock (Lock) a RWMutex and then
    // arrange for another goroutine to RUnlock (Unlock) it.
    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.
    	r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)
    	if r >= rwmutexMaxReaders {
    		race.Enable()
    		throw("sync: Unlock of unlocked RWMutex")
    	}
    	// Unblock blocked readers, if any.
    	for i := 0; i < int(r); i++ {
    		runtime_Semrelease(&rw.readerSem, false, 0)
    	}
    	// Allow other writers to proceed.
    	rw.w.Unlock()
    	if race.Enabled {
    		race.Enable()
    	}
    }
    
    // RLocker returns a Locker interface that implements
    // the Lock and Unlock methods by calling rw.RLock and 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() }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223

    RWMutex 是读写器互斥锁。
    锁可以由任意数量的读取器或单个写入器持有。
    RWMutex 的零值是未锁定的互斥锁。
    RWMutex 首次使用后不得复制。
    如果一个 goroutine 持有一个 RWMutex 用于读取,而另一个 goroutine 可能
    调用 Lock,没有 goroutine 应该期望能够获得读锁
    直到初始读锁被释放。 特别是,这禁止
    递归读锁定。 这是为了确保锁最终变为
    可用的; 阻塞的 Lock 调用将新读者排除在获取
    锁。
    实现的方法

    方法作用
    RLockRLock 锁定 rw 以供读取
    TryRLockTryRLock 尝试锁定 rw 以进行读取并报告它是否成功
    RUnlockRUnlock 撤消单个 RLock 调用;
    rUnlockSlow
    LockLock 锁定 rw 以进行写入。
    TryLockTryLock 尝试锁定 rw 进行写入并报告是否成功。
    UnlockUnlock 解锁 rw 以进行写入。 如果 rw 是运行时错误
    RLockerRLocker返回一个Locker接口,实现通过调用 rw.RLock 和 rw.RUnlock 的 Lock 和 Unlock 方法。

    三、golang读写锁结构及原理图

    golang读写锁结构:

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

    在这里插入图片描述

    读锁加锁逻辑

    在这里插入图片描述

    读锁释放逻辑

    在这里插入图片描述

    写锁实现
    golang rwmutex lock

    释放写锁

    在这里插入图片描述

    四、golang读写锁代码示例

    var (
        x      int64
        wg     sync.WaitGroup
        lock   sync.Mutex
        rwlock sync.RWMutex
    )
    func myadd() {
        // lock.Lock()   // 加互斥锁
        rwlock.Lock() // 加写锁
        x = x + 1
        time.Sleep(2 * time.Millisecond) // 假设写操作耗时2毫秒
        rwlock.Unlock()                   // 解写锁
        // lock.Unlock()                  // 解互斥锁
        wg.Done()
    }
    func myget() {
        // lock.Lock()                  // 加互斥锁
        rwlock.RLock()               // 加读锁
        time.Sleep(time.Millisecond) // 摸㧡读操作耗时1毫秒
        rwlock.RUnlock()             // 解读锁
        // lock.Unlock()             // 解互斥锁
        wg.Done()
    }
    func main() {
        start := time.Now()
        for i := 0; i < 10; i++ {
            wg.Add(1)
            go myadd()
        }
        for i := 0; i < 1000; i++ {
            wg.Add(1)
            go myget()
        }
        wg.Wait()
        end := time.Now()
        fmt.Println(end.Sub(start))
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    五、总结

    我们通过golang读写锁与 golang Mutex 对比得出答案。Mutex 是不区分 goroutine 对共享资源的操作行为的,在读操作、它会上锁,在写操作,它也会上锁,当一段时间内,读操作居多时,读操作在 Mutex 的保护下也不得不变为串行访问,对性能的影响也就比较大了。

    golang RWMutex 读写锁的诞生为了区分读写操作,在进行读操作时,goroutine 就不必傻傻的等待了,而是可以并发地访问共享资源,将串行读变成了并行读,提高了读操作的性能。

    读写锁针对解决一类问题:readers-writes ,同时有多个读或者多个写操作时,只要有一个线程在执行写操作,其他的线程都不能进行读操作。

    读写锁其实有三种工作模型:

    Read-perferring 优先读设计,可能会导致写饥饿
    Write-prferring 优先写设计,避免写饥饿
    不指定优先级 不区分优先级,解决饥饿问题

    Golang 中的读写锁,工作模型是 Write-prferring 方案。

  • 相关阅读:
    CSS 简介
    React源码学习(一):如何学习React源码
    android中的Bas64为何缺少 ==
    获取淘宝详情api接口(获取主图desc)
    组合总和 II
    怎么在dreamweaver嵌套盒子?
    h5钉钉导航栏实时更新(跟踪标题)标题
    RabbitMQ延迟消息指南【.NET6+EasyNetQ】
    echarts4_必知必会
    Linux学习-Linux系统及编程基础笔记
  • 原文地址:https://blog.csdn.net/itopit/article/details/125599761