关于取消的Golang上下文混淆

时间:2018-10-14 03:40:37

标签: go

package main

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

func myfunc(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            fmt.Printf("Ctx is kicking in with error:%+v\n", ctx.Err())
            return
        default:
            time.Sleep(15 * time.Second)
            fmt.Printf("I was not canceled\n")
            return
        }
    }
}

func main() {
    ctx, cancel := context.WithTimeout(
        context.Background(),
        time.Duration(3*time.Second))
    defer cancel()

    var wg sync.WaitGroup
    wg.Add(1)
    go func() {
        defer wg.Done()
        myfunc(ctx)
    }()

    wg.Wait()
    fmt.Printf("In main, ctx err is %+v\n", ctx.Err())
}

我上面的代码片段确实打印了这样的输出

I was not canceled
In main, ctx err is context deadline exceeded

Process finished with exit code 0

我知道context在3秒钟后超时,因此当我最后致电ctx.Err()时,确实给了我预期的错误。我还得到这样的事实,即在myfunc匹配select的情况下,一旦defaultdone的情况不匹配。我不明白的是,如何使用上下文逻辑在3秒内终止go func myfunc。基本上,它不会在3秒后终止,所以我试图了解golang的ctx如何帮助我?

2 个答案:

答案 0 :(得分:1)

在您的for ... select中,您有2种情况:case <-ctx.Done():default:。当您的代码到达select时,它进入default情况,因为上下文尚未被取消,它会休眠15秒然后返回,从而中断循环。 (换句话说,这不是在阻止/等待您的上下文取消)

如果您希望代码执行所描述的操作,则需要select来取消取消超时的情况。

for {
  select {
  case <-ctx.Done(): // context was cancelled
    fmt.Printf("Ctx is kicking in with error:%+v\n", ctx.Err())
    return
  case <-time.After(15 * time.Second): // 15 seconds have elapsed
    fmt.Printf("I was not canceled\n")
    return
  }
}

现在,您的代码在遇到select时将阻塞,而不是进入default大小写并破坏循环。

答案 1 :(得分:0)

如果要从上下文中使用超时和取消功能,则ctx.Done()需要同步处理。

https://golang.org/pkg/context/#Context的解释

  

Done返回一个通道,当取消代表该上下文的工作时,该通道已关闭。如果此上下文永远无法取消,则可能会返回nil。连续调用Done将返回相同的值。

因此,<-ctx.Done()基本上将在两个条件下被调用:

  1. 上下文超时超过
  2. 强制取消上下文时

当发生这种情况时,ctx.Err()将永远不会nil。接下来,我们可以对错误对象进行一些检查,以查看上下文是被强制取消还是超过了超时时间。

上下文包提供了两个错误对象context.DeadlineExceededcontext.Timeout,这两个错误对象将帮助我们轻松了解为什么执行<-ctx.Done()


使用场景的示例:超出上下文超时

在测试中,我们将尝试使上下文在超时之前被取消,因此将执行<-ctx.Done()

ctx, cancel := context.WithTimeout(
    context.Background(),
    time.Duration(3*time.Second))
defer cancel()

go func(ctx context.Context) {
    defer cancel()

    // simulate a process that takes 2 second to complete
    time.Sleep(2 * time.Second)
}(ctx)

select {
case <-ctx.Done():
    switch ctx.Err() {
    case context.DeadlineExceeded:
        fmt.Println("context timeout exceeded")
    case context.Canceled:
        fmt.Println("cancel the context by force")
    }
}

输出:

$ go run test.go 
cancel the context by force

使用场景的示例:超出上下文超时

在这种情况下,我们使过程花费的时间比上下文超时的时间长,因此理想情况下还将执行<-ctx.Done()

ctx, cancel := context.WithTimeout(
    context.Background(),
    time.Duration(3*time.Second))
defer cancel()

go func(ctx context.Context) {
    defer cancel()

    // simulate a process that takes 4 second to complete
    time.Sleep(4 * time.Second)
}(ctx)

select {
case <-ctx.Done():
    switch ctx.Err() {
    case context.DeadlineExceeded:
        fmt.Println("context timeout exceeded")
    case context.Canceled:
        fmt.Println("cancel the context by force")
    }
}

输出:

$ go run test.go 
context timeout exceeded