当在 RWMutex 解锁之后两次调用 RLock 时,goroutine 会被阻塞。

huangapple go评论83阅读模式
英文:

goroutine blocks when calling RWMutex RLock twice after an RWMutex Unlock

问题

var mu sync.RWMutex

go func() {
mu.RLock()
defer mu.RUnlock()

mu.RLock()  // 在我的真实场景中,这个第二个锁发生在一个嵌套函数中。
defer mu.RUnlock()

// 更多的代码。

}()

mu.Lock()
mu.Unlock() // 上面的 goroutine 仍然挂起。

如果一个函数两次对读写锁进行读锁定,而另一个函数对同一个锁进行写锁定然后解锁,原始函数仍然会挂起。

为什么会这样?是因为互斥锁按照一定的顺序允许代码执行吗?

我刚刚解决了一个类似的场景(花了我几个小时才找到问题所在),通过移除第二个 mu.RLock() 行来解决。

英文:
var mu sync.RWMutex

go func() {
    mu.RLock()
    defer mu.RUnlock()
    
    mu.RLock()  // In my real scenario this second lock happened in a nested function.
    defer mu.RUnlock()
    
    // More code.
}()

mu.Lock()
mu.Unlock()  // The goroutine above still hangs.

If a function read-locks a read/write mutex twice, while another function write-locks and then write-unlocks that same mutex, the original function still hangs.

Why is that? Is it because there's a serial order in which mutexes allow code to execute?

I've just solved a scenario like this (which took me hours to pinpoint) by removing the second mu.RLock() line.

答案1

得分: 10

这是读写锁的几种标准行为之一。维基百科称之为“偏向写的读写锁”

sync包的RWMutex.Lock方法的文档中写道:

> 为了确保锁最终可用,被阻塞的Lock调用会排除新的读取器获取锁。

否则,一系列在前一个读取器释放锁之前获取了读取锁的读取器可能会无限期地阻塞写入操作。

这意味着,在同一goroutine已经获取读取锁的情况下,调用RWMutexRLock方法总是不安全的。(顺便说一下,对于普通互斥锁的Lock方法也是如此,因为Go的互斥锁不支持递归锁定。)

不安全的原因是,如果goroutine由于被阻塞的写入操作而阻塞获取第二个读取锁,它将永远不会释放第一个读取锁。这将导致对互斥锁的每个未来锁定调用永远阻塞,从而死锁程序的一部分或全部。只有当所有goroutine都被阻塞时,Go才会检测到死锁。

英文:

This is one of several standard behaviours for a read-write lock. What Wikipedia calls "Write-preferring RW locks".

The documentation for sync's RWMutex.Lock says:

> To ensure that the lock eventually becomes available, a blocked Lock call excludes new readers from acquiring the lock.

Otherwise a series of readers that each acquired the read lock before the previous released it could starve out writes indefinitely.

This means that it is always unsafe to call RLock on a RWMutex that the same goroutine already has read locked. (Which by the way is also true of Lock on regular mutexes as well, as Go's mutexes do not support recursive locking.)

The reason it is unsafe is that if the goroutine ever blocks getting the second read lock (due to a blocked writer) it will never release the first read lock. This will cause every future lock call on the mutex to block forever, deadlocking part or all of the program. Go will only detect a deadlock if all goroutines are blocked.

huangapple
  • 本文由 发表于 2015年5月30日 23:27:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/30547916.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定