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
匹配的情况下default
,它不会匹配done
. 我不明白的是,如何go func myfunc
使用上下文逻辑让我在 3 秒内中止。基本上,它不会在 3 秒内终止,所以我试图了解 golang 如何ctx
帮助我解决这个问题?
解决方案
如果您想使用上下文中的超时和取消功能,那么在您的情况下需要ctx.Done()
同步处理。
来自https://golang.org/pkg/context/#Context的解释
Done 返回一个在代表此上下文完成工作时关闭的通道应该被取消。如果这个上下文永远不能被取消,Done 可能会返回 nil。对 Done 的连续调用返回相同的值。
所以基本上<-ctx.Done()
将在两个条件下调用:
- 当上下文超时超过
- 当上下文被强制取消时
当这种情况发生时,ctx.Err()
将永远不会nil
。
我们可以对错误对象进行一些检查,以查看上下文是否被强制取消或超过超时。
Context 包提供了两个错误对象,context.DeadlineExceeded
并且context.Timeout
,这两个将帮助我们识别为什么<-ctx.Done()
被调用。
示例 #1 场景:上下文被强制取消(通过cancel()
)
在测试中,我们会尝试让上下文在超时之前被取消,所以<-ctx.Done()
会被执行。
ctx, cancel := context.WithTimeout(
context.Background(),
time.Duration(3*time.Second))
go func(ctx context.Context) {
// simulate a process that takes 2 second to complete
time.Sleep(2 * time.Second)
// cancel context by force, assuming the whole process is complete
cancel()
}(ctx)
select {
case <-ctx.Done():
switch ctx.Err() {
case context.DeadlineExceeded:
fmt.Println("context timeout exceeded")
case context.Canceled:
fmt.Println("context cancelled by force. whole process is complete")
}
}
输出:
$ go run test.go
context cancelled by force
示例 #2 场景:超出上下文超时
在这种情况下,我们使进程花费的时间比上下文超时时间长,所以理想情况下<-ctx.Done()
也会执行。
ctx, cancel := context.WithTimeout(
context.Background(),
time.Duration(3*time.Second))
go func(ctx context.Context) {
// simulate a process that takes 4 second to complete
time.Sleep(4 * time.Second)
// cancel context by force, assuming the whole process is complete
cancel()
}(ctx)
select {
case <-ctx.Done():
switch ctx.Err() {
case context.DeadlineExceeded:
fmt.Println("context timeout exceeded")
case context.Canceled:
fmt.Println("context cancelled by force. whole process is complete")
}
}
输出:
$ go run test.go
context timeout exceeded
Example #3 场景:由于发生错误,上下文被强制取消
可能存在由于发生错误而需要在进程中间停止 goroutine 的情况。有时,我们可能需要在主程序中检索那个错误对象。
为此,我们需要一个额外的通道将错误对象从 goroutine 传输到主程序。
在下面的示例中,我准备了一个名为chErr
. 每当(goroutine)进程中间发生错误时,我们将通过通道发送该错误对象,然后立即停止进程。
ctx, cancel := context.WithTimeout(
context.Background(),
time.Duration(3*time.Second))
chErr := make(chan error)
go func(ctx context.Context) {
// ... some process ...
if err != nil {
// cancel context by force, an error occurred
chErr <- err
return
}
// ... some other process ...
// cancel context by force, assuming the whole process is complete
cancel()
}(ctx)
select {
case <-ctx.Done():
switch ctx.Err() {
case context.DeadlineExceeded:
fmt.Println("context timeout exceeded")
case context.Canceled:
fmt.Println("context cancelled by force. whole process is complete")
}
case err := <-chErr:
fmt.Println("process fail causing by some error:", err.Error())
}
附加信息#1:cancel()
在上下文初始化后立即调用
根据有关该功能的上下文文档:cancel()
取消此上下文会释放与其关联的资源,因此代码应在此上下文中运行的操作完成后立即调用取消。
最好总是cancel()
在上下文声明之后调用函数。它是否也在 goroutine 中被调用并不重要。这是因为确保在块内的整个过程完全完成时始终取消上下文。
ctx, cancel := context.WithTimeout(
context.Background(),
time.Duration(3*time.Second))
defer cancel()
// ...
附加信息#2:defer cancel()
在 goroutine 中调用
您可以在 goroutinedefer
中的cancel()
语句上使用(如果需要)。
// ...
go func(ctx context.Context) {
defer cancel()
// ...
}(ctx)
// ...
推荐阅读
- docker - Docker API /images/json 总是返回 Containers:-1
- go - go数据结构中的继承
- java - 如何检查 glassfish 上的 http 请求的完整响应时间?
- python - 在python中搜索查询
- speech-to-text - 使用其他数据重新训练 Azure 自定义语音模型
- php - 以另一个用户身份启动主管程序
- python - 在 Python 中从 bytearray 中提取 unsigned short int
- powershell - 抑制数组列表添加方法管道输出
- regex - 扫描电子邮件以查找包含大量正则表达式的代码的最佳方法
- python - 想要从用户那里获取 IP 地址以手动输入(而不是通过他们的位置)