登录
首页 >  Golang >  Go问答

避免跳过调用子上下文的取消函数的常见问题是什么?

来源:stackoverflow

时间:2024-02-12 13:06:27 390浏览 收藏

Golang小白一枚,正在不断学习积累知识,现将学习到的知识记录一下,也是将我的所得分享给大家!而今天这篇文章《避免跳过调用子上下文的取消函数的常见问题是什么?》带大家来了解一下##content_title##,希望对大家的知识积累有所帮助,从而弥补自己的不足,助力实战开发!


问题内容

问题描述

在 go 中使用“context”包时,如果不调用 context.withcancel() 返回的取消函数,go vet 命令的“lostcancel”检查将发出警告。例如如果你有:

func main() {
    ctx, cancel := context.withcancel(context.background())
    foo(ctx)
    fmt.println("done")
}

...然后兽医会给你警告:

取消函数并未在所有路径上使用(可能存在上下文泄漏)

context 包的文档中也提到了这一点:

未能调用 cancelfunc 会泄漏子级及其子级,直到父级被取消或计时器触发。 go vet 工具检查 cancelfuncs 是否在所有控制流路径上使用。

我的问题是这样的:至少在一个实例中,我不明白为什么必须调用取消函数。假设我想派生一个子上下文,以便我可以选择:

  1. 在取消父级上下文之前停止使用子上下文的下游 goroutine,例如遇到早期错误,或者
  2. 等到父上下文被取消,让子上下文与父上下文一样长。

在我看来,这是上下文包的父/子树结构的一个非常自然的用例。但这会产生与上面提到的相同的 go vet 错误,因为在代表第二个选项的代码执行路径中(让子进程与父进程的生存时间相同),子上下文的取消函数不会被调用。

那么,这真的是 go vet/go 社区使用 context 的指南的缺点吗?还是我对这个主题的思考存在缺陷?

示例程序

这是发生错误的示例程序。 go vet 会对 barctx, barcancel := context.withcancel(ctx) 行给出警告。这个例子显然很愚蠢,因为我可以在启动它的 goroutine 之前检查 bar 是否有错误。但我只想说:在我的实际程序中,这不是一个选项。

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

    time.Sleep(10 * time.Millisecond)
    fmt.Println("done")
}

func foo(ctx context.Context) {
    go func() {
        <-ctx.Done()
        fmt.Println("foo done")
    }()

    for i := 0; i < 2; i++ {
        barctx, barcancel := context.WithCancel(ctx)
        err := bar(barctx, i)
        if err != nil {
            fmt.Println("bar error; canceled")
            barcancel()
        }
    }
}

func bar(ctx context.Context, x int) (err error) {
    go func() {
        <-ctx.Done()
        fmt.Printf("bar done; x=%d\n", x)
    }()

    if x > 0 {
        err = fmt.Errorf("bar error")
    }
    return err
}

正确答案


在您的示例中, go vet 会正确返回警告。问题具体在于 bar 函数。当你创建它时,bar 创建一个 goroutine,该 goroutine 会阻塞,直到调用 barcancelcancel 为止。在那之前,它将继续存在。因此,您必须调用取消函数,以便等待上下文完成的资源可以超出范围并被垃圾收集。

需要明确的是,在您的示例中,所有延迟例程都将被取消,因为您取消了 ctx,这也取消了 barctx 的每个实例。但是,您不应该依赖于此,因为您可能无法控制是否取消父上下文。因此,您的函数应该对其在函数调用中创建和使用的上下文负责。

为了清楚起见,您应该像这样修改示例代码:

barctx, barcancel := context.WithCancel(ctx)
err := bar(barctx, i)
if err != nil {
    fmt.Println("bar error; canceled")
}

barcancel()

今天带大家了解了的相关知识,希望对你有所帮助;关于Golang的技术知识我们会一点点深入介绍,欢迎大家关注golang学习网公众号,一起学习编程~

声明:本文转载于:stackoverflow 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>