【问题标题】:goroutine blocks when calling RWMutex RLock twice after an RWMutex Unlock在 RWMutex Unlock 之后调用 RWMutex RLock 两次时,goroutine 阻塞
【发布时间】:2015-05-30 15:27:39
【问题描述】:
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.

如果一个函数读锁定了一个读/写互斥锁两次,而另一个函数写锁然后写-un锁定了同一个互斥锁,原来的函数仍然挂起。

这是为什么呢?是不是因为互斥体允许代码执行的顺序是串行的?

我刚刚通过删除第二个 mu.RLock() 行解决了这样的场景(我花了几个小时才确定)。

【问题讨论】:

  • 此代码是否始终如一地为您重现问题?我不能让它挂起来。

标签: concurrency go mutex


【解决方案1】:

这是读写锁的几个标准行为之一。什么Wikipedia calls "Write-preferring RW locks"

sync'sRWMutex.Lock 的文档说:

为确保锁最终可用,阻塞的 Lock 调用会阻止新的读取者获取锁。

否则,一系列读取器在前一个释放之前都获得了读取锁,它可能会无限期地耗尽写入。

这意味着在同一个 goroutine 已经读锁定的 RWMutex 上调用 RLock 总是不安全的。 (顺便说一下,Lock 在常规互斥锁上也是如此,因为 Go 的互斥锁不支持递归锁定。)

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

【讨论】:

  • 所以,总结一下:一个goroutine不应该RLock两次,或者Rlock然后Lock。原因是解锁有一个顺序(如队列)。
  • @OryBand;不完全的。单个 goroutine 不能 Lock 两次(第二次将永远阻塞RLock 相同,然后 Lock 相同 RWMutex)。并且单个 goroutine 不应该 RLock 两次相同的锁,不是因为任何排序(没有等待/解锁的队列),而是因为第二个 可以阻塞 (它不会总是)有效地在它已经持有的第一个锁上永远等待(实际上是在等待一个正在等待第一个锁的写入器)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-18
  • 1970-01-01
  • 1970-01-01
  • 2019-11-04
  • 1970-01-01
  • 2020-10-31
相关资源
最近更新 更多