【问题标题】:Explaining deadlocks with a single lock from The Little Go Book用 The Little Go Book 中的单锁解释死锁
【发布时间】:2023-03-05 21:50:01
【问题描述】:

我正在阅读The Little Go Book

第 76 页演示了如何使用单个锁进行死锁:

var (
    lock sync.Mutex
)

func main() {
    go func() { lock.Lock() }()
    time.Sleep(time.Millisecond * 10)
    lock.Lock()
}

按照作者的解释,运行它会导致死锁。但是,我不明白为什么。

我把程序改成了这样:

var (
    lock sync.Mutex
)

func main() {
    go func() { lock.Lock() }()
    lock.Lock()
}

我的预期是仍然会抛出死锁。但事实并非如此。

谁能给我解释一下这里发生了什么?

我能想到的唯一可以解释这一点的场景如下(但这是猜测):

第一个例子

  1. 在第一个 goroutine 中获取锁
  2. 调用time.Sleep() 确保获得锁
  3. main 函数尝试获取锁导致死锁
  4. 程序退出

第二个例子

  1. 在第一个 goroutine 中获得了锁,但这需要一些时间才能发生 (??)
  2. 由于没有延迟,main 函数在 goroutine 可以之前获取锁
  3. 程序退出

【问题讨论】:

  • 我认为程序在 goroutine 启动之前就退出了,没有延迟。
  • 另请注意,第一个示例也不能保证死锁,它只是在标准 Go 实现的大多数合理情况下发生。

标签: go mutex


【解决方案1】:

go所有 goroutines(包括主)休眠时显示死锁错误。

在您的第一个示例中,内部goroutine 在他调用mutex.Lock() 后被执行并终止。然后主 goroutine 再次尝试锁定,但它转到 asleep 等待占用锁定的机会。所以现在我们在程序中的所有goroutine(主要的)都处于睡眠模式,这将导致死锁错误!

理解这一点很重要,因为可能会发生死锁,但如果还有一个正在运行的 goroutine,它并不总是显示错误。这主要是生产中会发生的事情。只有当整个程序陷入死锁时才会报错。

【讨论】:

    【解决方案2】:

    在第一个例子中,main 的睡眠时间足够长,让子 goroutine 有机会启动并获取锁。然后该 goroutine 将愉快地退出而不释放锁。

    当 main 恢复其控制流时,共享互斥锁已锁定,因此下一次获取它的尝试将永远阻塞。由于此时 main 是唯一还活着的例程,因此永远阻塞会导致死锁。


    在第二个例子中,没有调用time.Sleep,main 直接继续获取锁。这成功了,所以 main 继续前进并退出。子 goroutine 永远阻塞,但是由于 main 已经退出,程序只是终止,没有死锁。

    顺便说一句,即使 main 没有退出,只要至少有一个没有阻塞的 goroutine,就不会出现死锁。为此,time.Sleep 不是阻塞操作,它只是在指定的持续时间内暂停执行。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-02-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-24
      • 2020-01-24
      • 1970-01-01
      相关资源
      最近更新 更多