Go并发编程-RWMutex
简单用
读写互斥锁RWMutex与互斥锁Mutex使用方法非常类似。在使用互斥锁Mutex时,并行会变成串行化,降低了效率,分析的时候就会发现读写如果能够分离开能极大的提高效率。也就是将串行读改为并行读,串行写保持不变。这是一个经典的readers-wriders问题。解决readers-writers有三种方案,read-prefer,write-prefer,不指定优先级。
-
Read-prefer:读优先,提高很好的并发性,但是会导致写饥饿问题。
-
Write-prefer:写优先,当有读请求时,新来的read请求会阻塞。解决了写饥饿问题
-
不区分优先级:没有优先,没有饥饿问题
RWMutex采用了write-prefer方案,一个正在阻塞的Lock调用会排除新的reader请求到锁。
type Counter struct {
mu sync.RWMutex
count int
}
func (c *Counter) Count() int {
c.mu.RLock()
defer c.mu.RUnlock()
return c.count
}
func (c *Counter) Incr() {
c.mu.Lock()
c.count++
c.mu.Unlock()
}
- 使用Mutex是不需要初始化的,在使用时会自动去初始化。所以在使用时不需要自己去创建一个对象
- Mutex变量声明时最好声明在其临界变量的上面,然后使用空格把字段分隔开来。逻辑会跟清晰,便于维护
- Lock和Unlock方法成对出现,使用defer去解锁。逻辑会跟清晰,便于维护,防止出现漏掉的情况
看实现
type RWMutex struct {
w Mutex // 互斥锁
writerSem uint32 // writer信号量
readerSem uint32 // reader信号量
readerCount int32 // reader的数量
readerWait int32 // writer等待完成的reader的数量
}
const rwmutexMaxReaders = 1 << 30
func (rw *RWMutex) RLock() {
if atomic.AddInt32(&rw.readerCount, 1) < 0 {
runtime_SemacquireMutex(&rw.readerSem, false, 0)
}
}
func (rw *RWMutex) RUnlock() {
if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {
rw.rUnlockSlow(r)
}
}
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_SemacquireMutex(&rw.writerSem, false, 0)
}
}
func (rw *RWMutex) Unlock() {
// 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()
}
rwmutexMaxReaders 定义了最大的reader的数量
readerCount:不仅表示reader的数量,同时也标记着writer竞争锁或者持有锁的状态
别踩坑
-
不是成对出现
要保证读写锁必须成对出现,不遗漏不多余。否则都会panic
-
不可复制
与Mutex不能复制的原因相同。sync的包中的同步原语是不能复制的,因为他们是有状态的对象,复制一个已经加锁的RWMutex给一个新的变量明显不符合预期。虽然这种情况很简单,但是还是会很容易出错,因为go的函数调用会自动复制。所以有关锁的参数传递要使用指针的方式。
-
重入锁
与Mutex相同,RWMutex也是不可重入锁。由于重入锁导致的死锁情况有很多。
- write锁重入调用Lock时发生死锁
func foo(rwm *sync.RWMutex) { fmt.Println("in foo") rwm.Lock() bar(rwm) rwm.Unlock() } func bar(rwm *sync.RWMutex) { rwm.Lock() fmt.Println("in bar") rwm.Unlock() } func main() { var rwm sync.RWMutex foo(&rwm) }
- 读写依赖导致死锁
func foo(rwm *sync.RWMutex) { fmt.Println("in foo") rwm.RLock() bar(rwm) rwm.RUnlock() } func bar(rwm *sync.RWMutex) { rwm.Lock() fmt.Println("in bar") rwm.Unlock() } func main() { var rwm sync.RWMutex foo(&rwm) }
- writer依赖reader. reader依赖新来的reader. 新来的reader依赖writer形成环状
func main() { var m sync.RWMutex go func() { time.Sleep(200 * time.Millisecond) m.Lock() fmt.Println("Lock") time.Sleep(100 * time.Millisecond) m.Unlock() fmt.Println("Unlock") }() factorial(&m, 10) } func factorial(m *sync.RWMutex, n int) int { if n < 1 { return 0 } fmt.Println("RLock") m.RLock() defer func() { fmt.Println("RUnlock") m.RUnlock() }() time.Sleep(100 * time.Millisecond) return factorial(m, n-1) * n }