【问题标题】:How to gracefully shutdown chained goroutines in an idiomatic way如何以惯用的方式优雅地关闭链式 goroutine
【发布时间】:2019-04-05 12:19:25
【问题描述】:

创建多个 goroutines,这些 goroutines 在以多级方式处理时将具有嵌套的 goroutines(想象一棵 goroutines 树,每个级别可以有很多叶子)。

按顺序优雅地关闭这些 goroutine 并等待它们返回的惯用方式是什么?顺序是底部顶部(最深的孩子优先),并且还假设我不知道我将预先启动多少个 goroutine(动态)。

下面的示例只是以无序的方式优雅地关闭它们。

package main

import (
    "context"
    "fmt"
    "time"
)

func main() {
    ctx := context.Background()
    ctx, cancel := context.WithCancel(ctx)

    //level1
    go func() {
        fmt.Println("level1 started")
        //level2
        go func() {
            fmt.Println("level2 started")

            //level3
            go func() {
                fmt.Println("level3 started")
                select {
                case <-ctx.Done():
                    fmt.Println("Done called on level3")
                case <-time.After(5* time.Second):
                    fmt.Println("After called on level3")
                }

            }()
            select {
            case <-ctx.Done():
                fmt.Println("Done called on level2")
            case <-time.After(7* time.Second):
                fmt.Println("After called on level2")
            }

        }()
        select {
        case <-ctx.Done():
            fmt.Println("Done called on level1")
        case <-time.After(10* time.Second):
            fmt.Println("After called on level1")
        }


    }()
    time.Sleep(1*time.Second)
    cancel()
    time.Sleep(1 * time.Second)
}

【问题讨论】:

  • 我强烈建议您评估您的设计,以找到一种方法来消除按顺序关闭的要求。您应该只需要同步并发数据访问,而且这种情况很少(当它们绝对必须共享内存时)。需要按顺序关闭 goroutine 意味着设计错误。

标签: go concurrency goroutine


【解决方案1】:

要等待一组 goroutine,sync.WaitGroup 是惯用的解决方案。当你启动一个新的 goroutine (WaitGroup.Add()) 时,你可以给它的计数器加 1,并且 goroutine 可以用WaitGroup.Done() 发出它已经完成的信号。父 goroutine 可能会调用 WaitGroup.Wait() 来等待其所有子进程完成。

您可以在每个级别上执行相同的操作。在启动子 goroutine 的每个级别上创建一个 WaitGroup,并且仅在该 goroutine 的 Wait() 返回时返回。

以下是它在您的示例中的应用方式:

ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)

//level1
wg1 := &sync.WaitGroup{}
wg1.Add(1)
go func() {
    defer wg1.Done()
    fmt.Println("level1 started")
    //level2
    wg2 := &sync.WaitGroup{}
    wg2.Add(1)
    go func() {
        defer wg2.Done()
        fmt.Println("level2 started")

        //level3
        wg3 := &sync.WaitGroup{}
        wg3.Add(1)
        go func() {
            defer wg3.Done()
            fmt.Println("level3 started")
            select {
            case <-ctx.Done():
                fmt.Println("Done called on level3")
            case <-time.After(5 * time.Second):
                fmt.Println("After called on level3")
            }
            fmt.Println("Level 3 ended.")
        }()

        select {
        case <-ctx.Done():
            fmt.Println("Done called on level2")
        case <-time.After(7 * time.Second):
            fmt.Println("After called on level2")
        }
        wg3.Wait()
        fmt.Println("Level 2 ended.")
    }()

    select {
    case <-ctx.Done():
        fmt.Println("Done called on level1")
    case <-time.After(10 * time.Second):
        fmt.Println("After called on level1")
    }
    wg2.Wait()
    fmt.Println("Level 1 ended.")
}()

time.Sleep(1 * time.Second)
cancel()
wg1.Wait()
fmt.Println("Main ended.")

这个输出(在Go Playground上试试):

level1 started
level2 started
level3 started
Done called on level1
Done called on level3
Level 3 ended.
Done called on level2
Level 2 ended.
Level 1 ended.
Parent ended.

输出中的重要内容:

Level 3 ended.
Level 2 ended.
Level 1 ended.
Main ended.

级别以降序(自下而上)结束,以"Main ended." 结束。

【讨论】:

  • 哦,哈哈...我只是浏览了实际问题并回答了标题中的问题:facepalm:
【解决方案2】:

一种可能的方法是通过strict{} 的通道来实现这一点。每当您希望上述 goroutine 终止时,只需向该通道写入一个空结构:shutdown &lt;- struct{}{}。这应该可以完成这项工作。 或者,您可以关闭通道,您可以通过将false 作为&lt;- 的第二个返回值来识别这一点,但我建议您仅在需要与多个 goroutine 共享此通道时才使用它。总的来说,我觉得这种方法有点粗制滥造,容易出错。

附带说明:在您的示例中关闭 goroutine 的方式是,一旦取消上下文,所有 goroutine 都将返回。不知道这在一般情况下是否有很大好处。也许在你的情况下是这样。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-04-22
    • 1970-01-01
    • 1970-01-01
    • 2013-01-28
    • 2012-03-25
    • 2014-12-01
    • 2014-11-03
    • 1970-01-01
    相关资源
    最近更新 更多