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
的情况下,一旦default
与done
的情况不匹配。我不明白的是,如何使用上下文逻辑在3秒内终止go func myfunc
。基本上,它不会在3秒后终止,所以我试图了解golang的ctx
如何帮助我?
答案 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()
基本上将在两个条件下被调用:
当发生这种情况时,ctx.Err()
将永远不会nil
。接下来,我们可以对错误对象进行一些检查,以查看上下文是被强制取消还是超过了超时时间。
上下文包提供了两个错误对象context.DeadlineExceeded
和context.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