英文:
Explaining deadlocks with a single lock from The Little Go Book
问题
我正在阅读《The Little Go Book》(https://www.openmymind.net/assets/go/go.pdf)。
第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()
}
我原本期望仍然会发生死锁,但实际上没有发生。
请问有人可以解释一下这里发生了什么吗?
我能想到的唯一情况是以下这种(但这只是猜测):
第一个例子
- 第一个 goroutine 获取了锁
- 调用
time.Sleep()
确保锁被获取 main
函数尝试获取锁,导致死锁- 程序退出
第二个例子
- 第一个 goroutine 获取了锁,但这需要一些时间(??)
- 由于没有延迟,
main
函数在 goroutine 之前获取了锁 - 程序退出
英文:
I'm reading The Little Go Book.
Page 76 demonstrates how you can deadlock with a single lock:
var (
lock sync.Mutex
)
func main() {
go func() { lock.Lock() }()
time.Sleep(time.Millisecond * 10)
lock.Lock()
}
Running this results in a deadlock as explained by the author. However, what I don't understand is why.
I changed the program to this:
var (
lock sync.Mutex
)
func main() {
go func() { lock.Lock() }()
lock.Lock()
}
My expectation was that a deadlock would still be thrown. But it wasn't.
Could someone explain to me what's happening here please?
The only scenario I can think of that explains this is the following (but this is guesswork):
First example
- The lock is acquired in the first goroutine
- The call to
time.Sleep()
ensures the lock is acquired - The
main
function attempts to acquire the lock resulting in a deadlock - Program exits
Second example
- The lock is acquired in the first goroutine but this takes some time to happen (??)
- Since there is no delay the
main
function acquires the lock before the goroutine can - Program exits
答案1
得分: 2
在第一个示例中,主函数休眠足够长的时间,以便子协程有机会启动并获取锁。然后,该协程会愉快地退出而不释放锁。
当主函数恢复其控制流时,共享的互斥锁被锁定,因此下一次尝试获取它将永远阻塞。由于此时只剩下主函数一个协程在运行,永久阻塞导致死锁。
在第二个示例中,如果没有调用time.Sleep
,主函数会直接尝试获取锁。这一次尝试成功,所以主函数继续执行并退出。子协程本来会永久阻塞,但由于主函数已经退出,程序会直接终止,没有死锁发生。
顺便说一下,即使主函数没有退出,只要至少有一个协程没有阻塞,就不会发生死锁。对于这个目的,time.Sleep
不是一个阻塞操作,它只是暂停执行指定的时间。
英文:
In the first example, main sleeps long enough to give the child goroutine the opportunity to start and acquire the lock. That goroutine then will merrily exit without releasing the lock.
By the time main resumes its control flow, the shared mutex is locked so the next attempt to acquire it will block forever. Since at this point main is the only routine left alive, blocking forever results in a deadlock.
<hr>
In the second example, without the call to time.Sleep
, main proceeds straight away to acquire the lock. This succeeds, so main goes ahead and exits. The child goroutine would then block forever, but since main has exited, the program just terminates, without deadlock.
By the way, even if main didn't exit, as long as there is at least one goroutine which is not blocking, there's no deadlock. For this purpose, time.Sleep
is not a blocking operation, it simply pauses execution for the specified time duration.
答案2
得分: 1
go
在所有 goroutine
(包括主 goroutine
)都处于休眠状态时会显示死锁错误。
在你的第一个示例中,内部的 goroutine
在调用 mutex.Lock()
后执行并终止。然后,主 goroutine
尝试再次锁定,但它进入休眠状态,等待机会来占用锁。现在,程序中的所有 goroutine
(包括主 goroutine
)都处于休眠模式,这将导致死锁错误!
理解这一点很重要,因为死锁可能会发生,但如果仍然有一个正在运行的 goroutine
,它不会总是显示错误。这在生产环境中通常会发生。只有当整个程序陷入死锁时才会报告错误。
英文:
go
shows the deadlock error when all goroutines
(including the main) are asleep.
in your first example, the inside goroutine
is executed and terminated after he call mutex.Lock()
. then the main goroutine tries to lock again but it goes asleep
waiting for the opportunity to occupy the lock. so now we have all the goroutines(the main one) in the program in asleep mode which will cause a deadlock error !
it is important to understand this because a deadlock may happen but it will not always show an error if there still a running goroutine. which mostly what will happen in production. error will be reported only when the whole program get in a deadlock.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论