当我运行此程序时:
package main
import (
"fmt"
"sync"
"time"
)
var mu = new(sync.Mutex)
func f2() {
mu.Lock()
fmt.Println("call f2...")
}
func main() {
go f2()
time.Sleep(time.Second * 2)
mu.Lock()
fmt.Println("get lock in main")
}
我得到以下输出:
call f2...
fatal error: all goroutines are asleep - deadlock!
我们知道具有死锁的条件有四种,一种是Hold and wait or resource holding
,它要求至少有两种资源才能获得,但是这里只有一种资源。
那么这是一个死锁吗?
我知道退出了很多并发编程。只是在这种情况下,只有f2()
个不释放锁定,并且不像wikipedia defines那样死锁。
答案 0 :(得分:3)
事前注意::“ goroutine死锁”(这是您遇到的情况)是所有现有goroutine被阻止并且无法自行执行(永远)的状态。有多个现有的goroutine或只有一个goroutine都没关系。如果go运行时检测到goroutine死锁,则终止应用程序(没有意义让该应用程序永久挂起,它将永远无法恢复,这在goroutine死锁的定义中)。
您的代码中有一个互斥锁,该互斥锁已锁定在2个goroutine中:在main
goroutine中,而另一个在执行f2()
中。
如果f2()
越早锁定互斥锁,主goroutine将永远无法再次锁定它,因为没有人解锁互斥锁,因此这是一个死锁。因为f2()
将在锁定和打印后返回,并且其goroutine将结束,并且仅剩下的main
goroutine在尝试锁定mu
时被阻止。并且由于您在main()
(作为goroutine)和锁定互斥锁之间的f2
中使用了2秒的睡眠,因此您会像往常一样观察该死锁。
请注意,如果安排main
goroutine首先锁定互斥锁,则不会发生死锁,因为main()
函数可以继续运行,并且一旦返回,应用程序将终止(不等待f2()
完成)。但是同样,由于您插入了睡眠,您可能永远不会获得此结果。
我使用了“总是喜欢” 和“不可能永远” 这样的短语,因为尽管睡眠指令对于goroutine调度程序来说是一个很好的调度点,但它不是同步点。运行时将安排其他goroutine在goroutine处于睡眠状态时运行(为什么不这样做),但这不能保证。同步点可以为您提供保证(例如通道通信,锁定,sync.Once
等)。这在The Go Memory Model中有详细说明。