【问题标题】:calling wait group done right after go routine starts?在goroutine启动后立即调用waitgroup?
【发布时间】:2022-07-06 08:14:23
【问题描述】:

https://go.dev/play/p/YVYRWSgcp4u

我在“Go 开发者的并发工具和技术”中看到了这段代码,其中提到了广播的用法,上下文是使用广播来唤醒三个 gorouting。

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {

    type Button struct {
        Clicked *sync.Cond
    }
    button := Button{Clicked: sync.NewCond(&sync.Mutex{})}

    subscribe := func(c *sync.Cond, fn func()) {
        var goroutineRunning sync.WaitGroup
        goroutineRunning.Add(1)
        go func() {
            goroutineRunning.Done() // <---- why here?
            //fmt.Println("wg already done")
            c.L.Lock()
            defer c.L.Unlock()
            c.Wait()
            fn()
            //goroutineRunning.Done(), if put here will result in deadlock, why?
            
        }()
        goroutineRunning.Wait()
    }

    var clickRegistered sync.WaitGroup
    clickRegistered.Add(3)

    subscribe(button.Clicked, func() {
        fmt.Println("Maximizing window.")
        clickRegistered.Done()
    })
    subscribe(button.Clicked, func() {
        fmt.Println("Displaying annoying dialog box!")
        clickRegistered.Done()
    })
    subscribe(button.Clicked, func() {
        fmt.Println("Mouse clicked.")
        clickRegistered.Done()
    })

    time.Sleep(time.Second * 3)
    button.Clicked.Broadcast()
    clickRegistered.Wait()

}

我正在尝试了解订阅部分

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

作者说:

这里我们定义了一个方便函数,它允许我们注册函数来处理来自条件的信号。每个处理程序都在自己的 goroutine 上运行,并且在确认该 goroutine 正在运行之前订阅不会退出。

我的理解是我们应该在gorouting里面defer goroutingRunning.Done()这样后面的代码(包括等待Cond和fn()调用,才有机会 运行),但在这种情况下,goroutingRunning.Done() 似乎必须在 gorouting 的开头,否则会导致死锁错误,但为什么呢?

-----更新------

我们实际上可以通过这种方式摆脱订阅功能中的等待组:

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

    var ClickRegistered sync.WaitGroup
    ClickRegistered.Add(3)
    go subscribe(button.Clicked, func() {
        fmt.Println("do 1")
    }, &ClickRegistered)
    go subscribe(button.Clicked, func() {
        fmt.Println("do 2")
    }, &ClickRegistered)
    go subscribe(button.Clicked, func() {
        fmt.Println("do 3")
    }, &ClickRegistered)

    time.Sleep(time.Millisecond * 50)
    fmt.Println("some process in main go routine finished")
    button.Clicked.Broadcast()
    ClickRegistered.Wait()

【问题讨论】:

    标签: go synchronization deadlock waitgroup


    【解决方案1】:

    这是一种确保当subscribe 返回时,goroutine 已经开始运行的机制。当 goroutine 启动时,它会调用 Done 向等待调用者发出 goroutine 正在运行的信号。如果没有这个机制,那么当 subscribe 返回时,goroutine 可能还没有被调度。

    延迟的Done 将不起作用,因为它只会在 goroutine 返回时运行,并且在条件变量发出信号之前不会发生。

    该方案不保证新的 goroutine 锁定互斥锁。这种模式是否真的有必要值得商榷。

    【讨论】:

    • 现在说得通了,非常感谢 Burak!
    • "如果没有这个机制,有可能当 subscribe 返回时,goroutine 还没有被调度。" - 为什么这很重要?如果在 goroutine 尚未调度时订阅返回,什么不会按预期工作?
    • @AndrewSavinykh,我说不出它为什么重要。该代码似乎是由认为确实如此的人编写的。
    猜你喜欢
    • 1970-01-01
    • 2018-11-04
    • 1970-01-01
    • 1970-01-01
    • 2012-09-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-05
    相关资源
    最近更新 更多