卓越飞翔博客卓越飞翔博客

卓越飞翔 - 您值得收藏的技术分享站
技术文章34450本站已运行393

我们如何保证取消的上下文会导致 goroutine 终止?

我们如何保证取消的上下文会导致 goroutine 终止?

php小编子墨将为大家介绍如何保证取消上下文会导致goroutine终止的方法。当我们在使用goroutine时,有时候需要在某个条件满足时取消它,以避免不必要的计算和资源浪费。为了确保goroutine能够正确终止,我们可以使用context包提供的机制。context包提供了一种在goroutine之间传递请求的方法,并且可以在需要时取消这些请求。通过合理地使用context包,我们可以确保goroutine在取消上下文时能够正确地终止,从而避免资源泄漏和其他潜在的问题。下面我们将详细介绍如何使用context包来实现这一目标。

问题内容

假设发生以下情况:

  • 我们有下面的 consumer 函数,在 goroutine 中运行。

  • 另一个 goroutine 正在 intchan 通道上毫无延迟地发送整数。换句话说,在 for 循环的每次迭代中,intchan 上都有一个准备好接收的值。

  • 启动 consumer goroutine 的 goroutine 已取消传递到 consumer 的上下文。因此,ctx.done() 通道也有一个可供接收的值。

问题:

  • 在这种情况下,select 语句的两种情况都已准备好运行。
  • 根据 go 之旅,select 将随机选择一种情况,因为两者都已准备好运行。
  • 如何保证 select 不会继续选择 案例?如果 案例在 for 循环的每次迭代中都准备就绪,我们如何知道 案例最终会被选择?
func consumer(ctx context.context, intchan chan int) {
    for {
        select {
        case <-ctx.done():
            return
        case i := <-intchan:
            foo(i)
        }
    }
}

我尝试在下面的程序中使用 consumer 函数。 在该程序的多次运行中,consumerproducer goroutine 似乎总是终止。

为什么我们最终不会以 <-ctx.done() 案例从未执行的运行结束?

package main

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

func main() {

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

    var wg sync.WaitGroup
    wg.Add(2) // add 2, because we spawn 2 goroutines

    Producer(ctx, &wg)

    fmt.Println(time.Now())
    time.Sleep(time.Second * 5) // cancel the context after 5 seconds

    cancelFunc()
    fmt.Println("CANCELLED")

    wg.Wait() // wait till both producer and consumer goroutines terminate
    fmt.Println(time.Now())

}

func Producer(ctx context.Context, wg *sync.WaitGroup) {
    intChan := make(chan int)

    go Consumer(ctx, intChan, wg)

    go func() {
        defer wg.Done()
        for {
            select {
            case <-ctx.Done():
                return
            case intChan <- 1:
            }
        }
    }()

}

func Consumer(ctx context.Context, intChan chan int, wg *sync.WaitGroup) {
    defer wg.Done()

    for {
        select {
        case <-ctx.Done():
            return
        case _ = <-intChan:
        }
    }
}

解决方法

没有任何保证。保证终止的最简单方法是在 select 语句之外使用 ctx.err() 检查错误。将错误返回到传递上下文的代码也很常见。我会这样编写 consumer 函数:

func Consumer(ctx context.Context, intChan chan int) error {
    for ctx.Err() == nil {
        select {
        case <-ctx.Done():
        case i := <-intChan:
            foo(i)
        }
    }
    return ctx.Err()
}
卓越飞翔博客
上一篇: 使用 Go 根据 /etc/shadow 文件中的哈希密码验证密码
下一篇: 返回列表
留言与评论(共有 0 条评论)
   
验证码:
隐藏边栏