Go 语言的读写锁真的是读不阻塞么

2021-08-20

发现一个神奇的问题:

package main

import (
	"fmt"
	"time"
	"sync"
)

func main() {
	var mu sync.RWMutex

	go func() {
		mu.RLock()
		time.Sleep(10*time.Second)
		mu.RUnlock()
	}()

	time.Sleep(100*time.Millisecond)

	// go func() {
	// 	mu.Lock()
	// 	mu.Unlock()
	// }()

	time.Sleep(100*time.Millisecond)

	start := time.Now()
	fmt.Println("before get read block")
	mu.RLock()
	fmt.Println("inside read load no block", time.Since(start))
	mu.RUnlock()
}

这一段代码,如果去掉注释的那一段...最后面的读锁会阻塞住!

也就是说,只存在读锁的时候,读读不阻塞。 但是当存在写锁的时候,读锁能阻塞读锁...这跟我之前的认知不太一样,用了这么久 Go 我居然都不知道自己的理解行为一直有问题。

查了一下官方的标准库文档是这么说的:

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.

这也就是上面代码看到的现象了:一个 goroutine 持有 RWMutex 的读锁,另一个 goroutine 调用写锁的时候,没有其它 goroutine 可以在最初的读锁释放之前,再获取到读锁。

-----------------

说说我怎么遇到这个问题的。弄了一个 map 容器,里面有放了地址到连接的映射。如果连接闲置了,就去回收掉。 用了一个读写锁,有 goroutine 在拿 map 里面的连接出去使用时,加上读锁。 回收不活跃连接时,加上写锁。 持有读锁之后,去发请求的操作有可能会卡住,但预期是不影响其它的连接继续的使用,或者说只是这一个地址的连接卡住。然而... duang! 当回收连接那个锁开始调用,哪怕还没有持有到写锁,读读也相互 block 了!

好啦,以后使用读写锁的代码,需要更加注意,读读也是可能阻塞的。

golangRWMutex