无法理解golang中的这种死锁情况,我有下面的带有pub和sub模式的go代码
package main
import (
"fmt"
"sync"
)
func main() {
cond := sync.NewCond(&sync.Mutex{})
subscribe := func(c *sync.Cond, fn func()) {
var goroutineRunning sync.WaitGroup
goroutineRunning.Add(1)
go func() {
goroutineRunning.Done()
fmt.Println("waiting")
c.L.Lock()
c.Wait()
c.L.Unlock()
fn()
}()
goroutineRunning.Wait()
}
var clickRegistered sync.WaitGroup
clickRegistered.Add(2)
subscribe(cond, func() {
fmt.Println("notified 1")
clickRegistered.Done()
})
subscribe(cond, func() {
fmt.Println("notified 2")
clickRegistered.Done()
})
cond.Broadcast()
clickRegistered.Wait()
}
我无法理解,为什么如果我把
fmt.Println("waiting")
放在goroutineRunning.Done()
之后,如果我删除fmt.Println("waiting")
或移动到goroutineRunning.Done()
之上,它会按预期工作,为什么会这样发生? ?
需要多次运行 go run main.go 才能获得死锁,第一次可能会起作用。
在您的代码中,调用
cond.Broadcast()
时有几种可能的情况:
c.Wait()
处被阻塞,然后被释放。c.Wait()
处被阻塞。cond.Broadcast()
唤醒所有等待 cond
的 goroutine,但是,根据上面的描述,有可能一个(或两个)goroutine 尚未达到其执行的这一点(因此不会等待 cond
,并且将不会被释放)。如果发生这种情况,他们将永远阻塞在 c.Wait()
,因为没有进一步的呼叫 cond.Broadcast()
。
所以我们这里有一场比赛;如果两个 Goroutine 在主 Goroutine 到达
c.Wait()
之前到达 cond.Broadcast()
,则程序完成。如果其中一个没有成功,程序就会死锁(主 goroutine 被阻塞在 clickRegistered.Wait()
处,1 或 2 个 goroutine 被阻塞在 c.Wait()
处。这样做的结果是程序的行为是不可预测的,并且取决于你的操作系统等因素, Go版本、CPU架构、当前负载等
您可以做一些可能会影响行为的事情。例如,在程序开始时添加对
runtime.GOMAXPROCS(1)
的调用,您可能(不能保证!)意味着它完成(即使使用 fmt.Println
)。这是因为当使用一个 CPU 运行时,Go 调度程序可能会允许每个 goroutine 运行到 c.Wait()
(这不能保证;调度程序可能会 抢占执行 - 也)。对于多个 CPU,goroutine 将(可能)跨多个内核运行,因此事情变得更不可预测。
在
goroutineRunning.Done()
上面添加代码不会有任何影响,因为在 Goroutines 调用 goroutineRunning.Wait()
之前,主 Goroutine 无法超越 goroutineRunning.Done()
。然而,该点之后的任何延迟(例如调用 fmt.Println("waiting")
)都会增加 Goroutine 在主 Goroutine 到达 c.Wait()
之前无法到达 cond.Broadcast()
的可能性(导致死锁)。