这个问题其实迷惑了我很久时间,今天写一下。
1. 见https://blog.csdn.net/qq_17612199/article/details/89786256,博文。
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx, cancel := context.WithTimeout(context.TODO(), time.Second*3)
defer cancel()
go task(ctx)
time.Sleep(time.Second * 10)
}
func task(ctx context.Context) {
ch := make(chan struct{}, 0)
go func() {
// 模拟4秒耗时任务
time.Sleep(time.Second * 4)
ch <- struct{}{}
}()
select {
case <-ch:
fmt.Println("done")
case <-ctx.Done():
fmt.Println("timeout")
}
}
本质上对代码的超时判断必须独立于主干逻辑代码的执行,由当前线程判断是超时还是业务逻辑做完。开启子线程执行业务逻辑。在子线程中如果进行超时判断,同样需要在子线程中进行示例代码的编写。
在增加一个我自己写的测试用例:
func main() { //这个 { 不能另起一行
ctx, _ := context.WithTimeout(context.TODO(), 2*time.Second)
go func(ctx context.Context) {
fmt.Printf("begin func1\n")
go func(ctx context.Context) {
ch := make(chan error, 0)
go func(ctx context.Context) {
fmt.Printf("begin func2\n")
time.Sleep(5 * time.Second)
if ctx.Err() != nil {
dealLine, dead := ctx.Deadline()
fmt.Printf("do not call end func2,deadLine:%v,dead:%v\n", dealLine, dead)
return
}
fmt.Printf("end func2\n")
ch <- nil
}(ctx)
select {
case <-ch:
fmt.Printf("ok")
case <-ctx.Done():
fmt.Printf("err:%v\n", ctx.Err())
}
}(ctx)
fmt.Printf("end func1\n")
}(ctx)
time.Sleep(10 * time.Second)
}
2. 比照java中netty框架的实现,超时的实现是采用时间轮操作。req发出后不管.收到rsp请求后,就从时间轮中删除该req。如果时间轮到期,则说明该req超时,需要进行超时处理(好像是这么实现的,时间久了,不记得了)
也就是超时时间的判断没有其他更好的方法,必须独立于主干业务逻辑中执行。
3. context中还有个方法,ctx.Err().如果超时了或者canel方法被调用了,则ctx.Err()是能返回对应的error错误的。因此有时候我们看到的错误提示,timeout exceed 什么的就是ctx.Err()返回的error错误。