当我运行这个程序时:
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()
没有释放锁,这并不是维基百科定义的死锁。
请注意:"goroutine死锁"(这就是您所经历的(是指所有现有goroutine都被阻塞并且无法单独进行的状态。存在多个现有goroutine或只有一个goroutine都无关紧要。如果go运行时检测到goroutine死锁,则终止应用程序(让应用程序永远挂起是没有意义的,它永远不会恢复,这在goroutine僵局的定义中(。
您的代码中有一个互斥锁,它被锁定在两个goroutine中:在main
goroutine和执行f2()
的goroutine。
如果f2()
提前到达锁定互斥体的点,那么主goroutine将永远无法再次锁定它,因为没有人解锁互斥体,所以这就是死锁。因为f2()
将在锁定和打印后返回,并且它的goroutine将结束,并且唯一剩余的main
goroutine在尝试锁定mu
时被阻止。由于在main()
中,在启动f2
(作为goroutine(和锁定互斥体之间使用了2秒的睡眠,因此您将像往常一样观察到这种死锁。
请注意,如果main
goroutine将被调度为首先锁定互斥,则不会发生死锁,因为main()
函数可以继续运行,并且一旦返回,应用程序将终止(不等待f2()
完成(。但同样,由于你插入的睡眠,你很可能永远不会得到这个结果。
我使用了短语"像往常一样">和"可能永远不会">,因为尽管睡眠指令是goroutine调度器的一个很好的调度点,但它不是同步点。运行时会安排其他goroutine在goroutine处于睡眠状态时运行(为什么不这样做(,但这并不能保证。同步点可以为您提供保证(如信道通信、锁、sync.Once
等(。这在Go内存模型中有详细说明。