使用 The Little Go Book 中的单锁解释死锁

Explaining deadlocks with a single lock from The Little Go Book

我正在阅读 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. 之前获取锁
  4. 程序退出

在第一个例子中,main sleep 足够长的时间让子 goroutine 有机会启动并获取锁。然后那个 goroutine 会在不释放锁的情况下愉快地退出。

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


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

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

go显示当所有 goroutines(包括主要)都在睡觉时出现死锁错误。

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

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