商家入驻型网站建设,哈密北京网站建设,企业微信app下载安装官网,页游最火的游戏✍个人博客#xff1a;Pandaconda-CSDN博客 #x1f4e3;专栏地址#xff1a;http://t.csdnimg.cn/UWz06 #x1f4da;专栏简介#xff1a;在这个专栏中#xff0c;我将会分享 Golang 面试中常见的面试题给大家~ ❤️如果有收获的话#xff0c;欢迎点赞#x1f44d;收藏… ✍个人博客Pandaconda-CSDN博客 专栏地址http://t.csdnimg.cn/UWz06 专栏简介在这个专栏中我将会分享 Golang 面试中常见的面试题给大家~ ❤️如果有收获的话欢迎点赞收藏您的支持就是我创作的最大动力 4. R WMutex 实现
sync.RWMutex 是 Go 语言标准库提供的一种读写锁用于在多个 goroutine 同时访问共享资源时进行保护。与 sync.Mutex 类似sync.RWMutex 也是通过互斥锁实现的但是它允许多个 goroutine 同时获取读锁而只允许一个 goroutine 获取写锁。
下面是一个简单的 sync.RWMutex 的实现示例
type RWMutex struct {writerSem chan struct{} // 写者用的信号量readerSem chan struct{} // 读者用的信号量readerCount int // 当前持有读锁的goroutine数量writerCount int // 当前持有写锁的goroutine数量readerWait int // 正在等待读锁的goroutine数量writerWait int // 正在等待写锁的goroutine数量writerLocked bool // 是否有goroutine持有写锁
}
func NewRWMutex() *RWMutex {return RWMutex{writerSem: make(chan struct{}, 1),readerSem: make(chan struct{}, 1),}
}
// 获取读锁
func (m *RWMutex) RLock() {// 获取读锁的过程需要加锁m.writerSem - struct{}{} // 防止写者获取锁m.readerSem - struct{}{} // 获取读锁的信号量// 更新状态m.readerCountif m.writerLocked || m.writerWait 0 {m.readerWait-m.readerSem // 等待写者释放锁m.readerWait--}// 释放加锁时获取的信号量-m.writerSem
}
// 释放读锁
func (m *RWMutex) RUnlock() {// 获取读锁的过程需要加锁m.writerSem - struct{}{} // 防止写者获取锁// 更新状态m.readerCount--if m.readerCount 0 m.writerWait 0 {-m.writerSem // 优先唤醒写者}// 释放加锁时获取的信号量-m.writerSem
}
// 获取写锁
func (m *RWMutex) Lock() {// 获取写锁的过程需要加锁m.writerSem - struct{}{} // 防止其他写者获取锁m.writerWait// 等待其他goroutine释放读锁或写锁for m.writerLocked || m.readerCount 0 {-m.readerSem}// 更新状态m.writerWait--m.writerLocked true// 释放加锁时获取的信号量-m.writerSem
}
// 释放写锁
func (m *RWMutex) Unlock() {// 获取写锁的过程需要加锁m.writerSem - struct{}{}// 更新状态m.writerLocked falseif m.writerWait 0 {-m.writerSem} else if m.readerWait 0 {for i : 0; i m.readerCount; i {m.readerSem - struct{}{} // 优先唤醒读者}}// 释放加锁时获取的信号量-m.writerSem
}
在这个实现中sync.RWMutex 包含以下成员 writerSem 和 readerSem两个用于同步的信号量通道。写锁会在 writerSem 上等待读锁会在 readerSem 上等待。 readerCount 和 writerCount当前持有读锁和写锁的 goroutine 数量。 readerWait 和 writerWait正在等待读锁和写锁的 goroutine 数量。 writerLocked标记当前是否有 goroutine 持有写锁。
在读锁和写锁获取和释放的过程中都需要先获取 writerSem 信号量防止其他写者获取锁。获取读锁时还需要获取 readerSem 信号量而获取写锁时需要等待其他 goroutine 释放读锁或写锁。
这个实现中有两个重要的细节 优先唤醒写者在释放读锁或写锁时如果有正在等待的写锁 goroutine应该优先唤醒它们因为写锁的优先级更高。 读锁的等待问题在等待读锁的 goroutine 中如果有其他 goroutine 正在持有写锁或等待写锁那么这些读锁 goroutine 应该等待写锁 goroutine 释放锁避免因等待读锁而导致写锁饥饿。
5. R WMutex 注意事项 RWMutex 是单写多读锁该锁可以加多个读锁或者一个写锁。 读锁占用的情况下会阻止写不会阻止读多个 Goroutine 可以同时获取读锁。 写锁会阻止其他 Goroutine无论读和写进来整个锁由该 Goroutine 独占。 适用于读多写少的场景。 RWMutex 类型变量的零值是一个未锁定状态的互斥锁。 RWMutex 在首次被使用之后就不能再被拷贝。 RWMutex 的读锁或写锁在未锁定状态解锁操作都会引发 panic。 RWMutex 的一个写锁去锁定临界区的共享资源如果临界区的共享资源已被读锁或写锁锁定这个写锁操作的 goroutine 将被阻塞直到解锁。 RWMutex 的读锁不要用于递归调用比较容易产生死锁。 RWMutex 的锁定状态与特定的 goroutine 没有关联。一个 goroutine 可以 RLockLock另一个 goroutine 可以 RUnlockUnlock。 写锁被解锁后所有因操作锁定读锁而被阻塞的 goroutine 会被唤醒并都可以成功锁定读锁。 读锁被解锁后在没有被其他读锁锁定的前提下所有因操作锁定写锁而被阻塞的 Goroutine其中等待时间最长的一个 Goroutine 会被唤醒。 6. Go 读写锁的实现原理
概念
读写互斥锁 RWMutex是对 Mutex 的一个扩展当一个 goroutine 获得了读锁后其他 goroutine 可以获取读锁但不能获取写锁当一个 goroutine 获得了写锁后其他 goroutine 既不能获取读锁也不能获取写锁只能存在一个写者或多个读者可以同时读。
使用场景
读多于写的情况既保证线程安全又保证性能不太差。
底层实现结构
互斥锁对应的是底层结构是 sync.RWMutex 结构体位于 src/sync/rwmutex.go 中
type RWMutex struct {w Mutex // 复用互斥锁writerSem uint32 // 信号量用于写等待读readerSem uint32 // 信号量用于读等待写readerCount int32 // 当前执行读的 goroutine 数量readerWait int32 // 被阻塞的准备读的 goroutine 的数量
}
操作:
读锁的加锁与释放
func (rw *RWMutex) RLock() // 加读锁
func (rw *RWMutex) RUnlock() // 释放读锁
加读锁
func (rw *RWMutex) RLock() {
// 为什么readerCount会小于0呢往下看发现writer的Lock()会对readerCount做减法操作原子操作if atomic.AddInt32(rw.readerCount, 1) 0 {// A writer is pending, wait for it.runtime_Semacquire(rw.readerSem)}
}
atomic.AddInt32(rw.readerCount, 1) 调用这个原子方法对当前在读的数量加 1如果返回负数那么说明当前有其他写锁这时候就调用 runtime_SemacquireMutex 休眠当前 goroutine 等待被唤醒。
释放读锁
解锁的时候对正在读的操作减 1如果返回值小于 0 那么说明当前有在写的操作这个时候调用 rUnlockSlow 进入慢速通道。
func (rw *RWMutex) RUnlock() {if r : atomic.AddInt32(rw.readerCount, -1); r 0 {rw.rUnlockSlow(r)}
}
被阻塞的准备读的 goroutine 的数量减 1readerWait 为 0就表示当前没有正在准备读的 goroutine 这时候调用 runtime_Semrelease 唤醒写操作。
func (rw *RWMutex) rUnlockSlow(r int32) {// A writer is pending.if atomic.AddInt32(rw.readerWait, -1) 0 {// The last reader unblocks the writer.runtime_Semrelease(rw.writerSem, false, 1)}
}
写锁的加锁与释放。
func (rw *RWMutex) Lock() // 加写锁
func (rw *RWMutex) Unlock() // 释放写锁
加写锁
const rwmutexMaxReaders 1 30
func (rw *RWMutex) Lock() {// 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_Semacquire(rw.writerSem)}
}
首先调用互斥锁的 lock获取到互斥锁之后如果计算之后当前仍然有其他 goroutine 持有读锁那么就调用 runtime_SemacquireMutex 休眠当前的 goroutine 等待所有的读操作完成
这里readerCount 原子性加上一个很大的负数是防止后面的协程能拿到读锁阻塞读
释放写锁
func (rw *RWMutex) Unlock() {// Announce to readers there is no active writer.r : atomic.AddInt32(rw.readerCount, rwmutexMaxReaders)// Unblock blocked readers, if any.for i : 0; i int(r); i {runtime_Semrelease(rw.readerSem, false)}// Allow other writers to proceed.rw.w.Unlock()
}
解锁的操作会先调用 atomic.AddInt32(rw.readerCount, rwmutexMaxReaders) 将恢复之前写入的负数然后根据当前有多少个读操作在等待循环唤醒
注意点 读锁或写锁在 Lock() 之前使用 Unlock() 会导致 panic 异常。 使用 Lock() 加锁后再次 Lock() 会导致死锁不支持重入需 Unlock() 解锁后才能再加锁。 锁定状态与 goroutine 没有关联一个 goroutine 可以 RLockLock另一个 goroutine 可以 RUnlockUnlock。
互斥锁和读写锁的区别 读写锁区分读者和写者而互斥锁不区分。 互斥锁同一时间只允许一个线程访问该对象无论读写读写锁同一时间内只允许一个写者但是允许多个读者同时读对象。