简体   繁体   English

为什么 Go function 中的 go 函数需要等待组才能正确退出?

[英]Why go func in Go function needs waitgroup to exit correctly?

Sry this title might be misleading.抱歉,这个标题可能具有误导性。 Actually the full code is here below:实际上完整的代码如下:

package main

import (
    "fmt"
    "sync"
)

type Button struct {
    Clicked *sync.Cond
}

func main() {
    button := Button{
        Clicked: sync.NewCond(&sync.Mutex{}),
    }
    subscribe := func(c *sync.Cond, fn func()) {
        var wg sync.WaitGroup
        wg.Add(1)
        go func() {
            wg.Done()
            c.L.Lock()
            defer c.L.Unlock()
            c.Wait()
            fn()
        }()
        wg.Wait()
    }

    var clickRegistered sync.WaitGroup
    clickRegistered.Add(2)

    subscribe(button.Clicked, func() {
        fmt.Println("maximizing window")
        clickRegistered.Done()
    })
    subscribe(button.Clicked, func() {
        fmt.Println("displaying dialog")
        clickRegistered.Done()
    })

    button.Clicked.Broadcast()
    clickRegistered.Wait()
}

When I comment some lines and run it again, it throws a fatal error "all goroutines are asleep - deadlock!"当我注释一些行并再次运行它时,它会抛出一个致命错误"all goroutines are asleep - deadlock!"
The subscribe function altered looks like as below:订阅 function 更改如下所示:

subscribe := func(c *sync.Cond, fn func()) {
        //var wg sync.WaitGroup
        //wg.Add(1)
        go func() {
            //wg.Done()
            c.L.Lock()
            defer c.L.Unlock()
            c.Wait()
            fn()
        }()
        //wg.Wait()
    }

What makes me confused is that whether go func is executed before the outer subscribe function returns.让我困惑的是, go func是否在外部subscribe function 返回之前执行。 In my thought, the go func will run as a daemon though the outer function has returned, so the wg variable is unnecessary.在我看来,尽管外部 function 已返回,但go func将作为守护进程运行,因此wg变量是不必要的。 But it shows I'm totally wrong.但这表明我完全错了。 So if the go func has the possibility of not being scheduled, does it mean that we must use the sync.WaitGroup in every function or code block to make sure the goroutine is scheduled to be executed before the function or code block returns?那么如果go func有没有被调度的可能,是不是意味着我们必须在每个 function 或代码块中使用sync.WaitGroup来确保 goroutine 被调度在 ZC1C4145268E1AB74F 或代码块返回7A985D 之前执行?
Thank you all.谢谢你们。

With the wg waitgroup (as coded in your current group): when the subscribe function returns, you know that the waiting goroutine has at least started its execution.使用wg等待组(如您当前组中的编码):当subscribe function 返回时,您知道等待的 goroutine 至少已经开始执行。

So when your main function reaches button.Clicked.Broadcast() , there's a good chance the two goroutines are actually waiting on their button.Clicked.Wait() call.因此,当您的主要 function 到达button.Clicked.Broadcast()时,这两个 goroutine 很有可能实际上正在等待他们的button.Clicked.Wait()调用。

Without the wg , you have no guarantee that the goroutines have even started, and your code may call button.Clicked.Broadcast() too soon.如果没有wg ,您甚至无法保证 goroutines 已经启动,并且您的代码可能会过早调用button.Clicked.Broadcast()


Note that your use of wg merely makes it less probable for the deadlock to happen, but it won't prevent it in all cases.请注意,您使用wg只会降低发生死锁的可能性,但不会在所有情况下都阻止它。

Try compiling your binary with -race , and run it in a loop (eg from bash: for i in {1..100}; do./myprogram; done ), I think you will see that the same problem happens sometimes.尝试使用-race编译你的二进制文件,并在循环中运行它(例如,从 bash: for i in {1..100}; do./myprogram; done ),我想你会看到有时会发生同样的问题。

The problem is that c.Wait() in either call is not guaranteed to run before button.Clicked.Broadcast() ;问题是c.Wait()不能保证在button.Clicked.Broadcast()之前运行; and even your original code's use of WaitGroup does not guarantees it either (since it is the c.Wait() part, not the spawn of the goroutine that is important)甚至您的原始代码对WaitGroup的使用也不能保证它(因为它是c.Wait()部分,而不是重要的 goroutine 的产生)

modified subscribe:修改订阅:

subscribe := func(c *sync.Cond, subWG *sync.WaitGroup, fn func()) {
        go func() {
            c.L.Lock()
            defer c.L.Unlock()
            subWG.Done() // [2]
            c.Wait()
            fn()
        }()
    }

code of waiting:等待代码:

subWG.Done()
button.Clicked.L.Lock()
button.Clicked.L.Unlock()

This is based on the observation that [2] can only happen either at the beginning or after the all previous goroutines that execute [2] is holding on c.Wait , due to the locker they shared.这是基于这样的观察,即[2]只能在开始时或在所有先前执行[2]的 goroutine 持有c.Wait ,因为它们共享的储物柜。 So subWG.Wait() , meaning that 2 (or number of the subscriptions) [2] is executed, it is only possible that one goroutine is not holding on c.Wait , which can be solved by asking for the locker to Lock another time.所以subWG.Wait() ,意味着执行了2 (或订阅的数量) [2] ,只有一个 goroutine 没有持有c.Wait的可能性是,这可以通过要求储物柜Lock另一个来解决时间。

Playground: https://play.golang.org/p/6mjUEcn3ec5游乐场: https://play.golang.org/p/6mjUEcn3ec5

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM