背景
学一个东西之前应该先明白他到底是干什么的,如果这个东西没有适用的场景,那你看他的源码就是浪费时间。
四种context
- emptyCtx 实现了一个空的context,可以用作根节点
- cancelCtx 实现一个带cancel功能的context,可以主动取消
- timerCtx实现一个通过定时器timer和截止时间deadline定时取消的context
- valueCtx 实现一个可以通过 key、val两个字段来存数据的context
context能干什么?
- 传递上下文
比如一个请求过来后,调用了服务A,服务A又调用了服务B,B又调用了C…,那么想在这些服务中间共享一些信息,例如链路追踪的trace_id,可以使用 valueCtx ,这样服务A、B、C都可以通过 ctx.Value(“trace-id”) 获取。
- 控制协程关闭
还是一个请求过来,调用了服务A,服务A又调用了服务B,B又调用了C…,如果当请求调用到A、B、C还没有得到结果时就关闭了请求,那么再调用A、B、C就是没有任何意义的,那么就可以利用ctx, cancel := context.WithCancel(context.Background()),让服务A、B、C都
监听ctx.done(), 当在请求调用cancel()时,A、B、C服务看到请求取消了调用,就不在继续执行,省的浪费资源。
- 控制超时取消
我们后端的服务一般都会设置一个超时时间,比如A服务调用B服务,如果A调用B 5秒后没有得到响应,就重新发起请求,就可以用到ctx, _ := context.WithTimeout(context.Background(), 5000*time.Millisecond)。当然了你说我自己实现一个定时器不就完了么?但是你要考虑 如果有条件换成了 A调用B,B又调用C这种情况,用上下文的优势就出来了,可以在A调用B超过5秒后也关闭B对C的调用防止资源浪费,当然了这个层级越深优势越大。
原理
当然你可以去看源码,我只是说核心的实现。
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
关键的还是这个Done()方法
Done():返回一个只读chan,如果可以从该 chan 中读取到数据,则说明 ctx 被取消了
如果我们自己想实现一个取消是这么写。
done := make(chan struct{})
go func() {
for {
select {
case <-done:
fmt.Println("cancel")
time.Sleep(1000*time.Millisecond)
default:
fmt.Println("not cancel")
time.Sleep(1000*time.Millisecond)
}
}
}()
fmt.Println("working")
time.Sleep(1000*time.Millisecond)
fmt.Println("working")
time.Sleep(1000*time.Millisecond)
close(done)
其实context和我们自己写的也差不多,只是他有一个层级的关系,就是前面写到的A调用B、B又调用C,所以层级就是A是B的parent,B是C的parent,当A进行取消的时候也会将B、C取消,说白了就是当A调用close(done)时候,B、C也会调用close(done),只是这个复杂的层级关系context这个包帮你做了,cancelCtx 、timerCtx本质区别就是多了一个定时器,一个是你自己取消,一个是到期了自动帮你取消,valueCtx就是让服务A、B、C(无论多深)都可以获取到某个key=>value对,实现思路也简单,就是一层一层向上的找,比如从服务C执行ctx.Value(“trace-id”) 没有话就去parent B找,B没找到就去A找,A也没找到就是没有了。
总结
知道了应用场景。知道了核心原理,用起来也就没那么难了。
参考
https://zhuanlan.zhihu.com/p/420127690