GO Cond - wg.Done 之后的 fmt.Println 最终陷入死锁

问题描述 投票:0回答:1

无法理解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 才能获得死锁,第一次可能会起作用。

go concurrency locking mutex goroutine
1个回答
0
投票

在您的代码中,调用

cond.Broadcast()
时有几种可能的情况:

  • 两个 goroutine 在
    c.Wait()
    处被阻塞,然后被释放。
  • 其中一个(或两个)goroutine 尚未在
    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()
的可能性(导致死锁)。

© www.soinside.com 2019 - 2024. All rights reserved.