参考:https://www.cnblogs.com/thinkeridea/p/10177781.html
总结下,就是golang的读写锁是写优先的,如果同时有读锁和写锁去竞争,会优先给写锁;但是如果有已经获取到读锁的,写锁也会等已经获取的读锁执行完毕后再去竞争。文章里面举得例发生的场景就是,正在进行的读锁->竞争的写锁->竞争的读锁,最终导致的问题就是读写锁互相等待,导致死锁。
复现问题:
package main
import (
"fmt"
"runtime"
"sync"
)
var l = sync.RWMutex{}
func main() {
var wg sync.WaitGroup
wg.Add(2)
c := make(chan int)
go func() {
l.RLock() // 读锁1
defer l.RUnlock()
fmt.Println(1)
c <- 1
fmt.Println(2)
runtime.Gosched()
fmt.Println(3)
b()
fmt.Println(4)
wg.Done()
}()
go func() {
fmt.Println(5)
<-c
fmt.Println(6)
l.Lock()
fmt.Println(7)
fmt.Println(8)
defer l.Unlock()
fmt.Println(9)
wg.Done()
}()
go func() {
i := 1
for {
i++
}
}()
wg.Wait()
}
func b() {
fmt.Println(10)
l.RLock() // 读锁2
fmt.Println(11)
defer l.RUnlock()
fmt.Println(12)
}
执行的结果
分析:
- 首先加上读锁1,就是
fmt.Println(1)
之前, 状态加读锁1 - 另外一个
goroutine
启动,fmt.Println(5)
, 状态加读锁1 - 发送数据
c <- 1
, 状态加读锁1 - 接受到数据
<-c
fmt.Println(6)
, 状态加读锁1 - 输出 2
fmt.Println(2)
, 状态加读锁1 - 暂停当前
goroutine
runtime.Gosched()
, 状态加读锁1 - 申请写锁
l.Lock()
, 等待读锁1释放, 状态加读锁1、写锁等待 - 切换
goroutine
执行fmt.Println(3)
与b()
, 状态加读锁1、写锁等待 - 输出10
fmt.Println(10)
, 申请读锁2,等待写锁释放, 状态加读锁1、写锁等待、读锁2等待 - 支持程序永久阻塞……
Tip:
- 运行时离开当前逻辑就释放锁。
- 锁的粒度越小越好,加锁后尽快释放锁。
- 尽量不用
defer
释放锁。 - 读锁不要嵌套。