1.上下文Context的实现
1. 常用场景
并发安全的传递上下文,如传递 trace_id、kv 结构等
树状并发情景中的协程控制(某层任务取消后,及时停止所有的下层任务,上层不受影响;减少额外资源的消耗,减少处理时长)
超时控制
1.1 使用例子
func main() {
ctx1, cancel1 := context.WithTimeout(context.Background(), time.Second) // 1s的超时
defer cancel1() // 保证返回前调用一次 cacel。这样比如主协程 panic 时,就可以调用到 cancel 函数,释放子协程资源
req1, _ := http.NewRequestWithContext(ctx1, http.MethodGet, "", nil)
go A(req1)
}
func A(r http.Request) {
... // 一些耗时的处理逻辑
B(r)
}
func B(r http.Request) {
select {
// B这里应当先检查是否超时。如果超时,则提现返回,减少链路整理时长
case <-ctx.Done():
return
default:
}
}2. 底层结构
context.Context 是 Go 语言在 1.7 版本中引入标准库的接口,该接口定义了四个需要实现的方法,其中包括:
为啥不将这里的 canceler 接口与 Context 接口合并,而是分成两个方法呢?他们定义的方法中都有 Done 方法。可以解释得通的说法是,源码作者认为 cancel 方法并不是 Context 必须的,根据最小接口设计原则,将两者分开。像 emptyCtx 和 valueCtx 不是可取消的,所以他们只要实现 Context 接口即可。
库里头提供了4个 Context 实现,来供大家玩耍
3. 具体实现
cancelCtx
cancel方法的实现
其实主要就做了一件事:close(c.done) 把 c.done 这个 channel 关闭掉,这样所有通过 select 方法监听 <- c.Done() 的协程都会收到通知
WithCancel
valueCtx
timerCtx
WithTimeout & WithDeadline
WithDeadline 内置了 timer 定时器,到期就执行 cancel
Withtimeout 实际上是 Withdeadline 的语法糖,使用 timeout 参数算了下 deadline 而已
4. net/http中的实际应用
首先
Server在开启服务时会创建一个valueCtx, 存储了server的相关信息,之后每建立一条连接就会开启一个协程,并携带此valueCtx。
建立连接之后会基于传入的
context创建一个valueCtx用于存储本地地址信息,之后在此基础上又创建了一个cancelCtx,然后开始从当前连接中读取网络请求,每当读取到一个请求则会将该cancelCtx传入,用以传递取消信号。一旦连接断开,即可发送取消信号,取消所有进行中的网络请求。
读取到请求之后,会再次基于传入的
context创建新的cancelCtx,并设置到当前请求对象req上,同时生成的response对象中cancelCtx保存了当前context取消的方法。
在整个server处理流程中,使用了一条 context 链贯穿Server、Connection、Request,不仅将上游的信息共享给下游任务,同时实现了上游可发送取消信号取消所有下游任务,而下游任务自行取消不会影响上游任务。
另外有两点值得注意:
context的取消是柔性的,上游任务仅仅使用context通知下游不再需要,不会直接干涉和中断下游任务的执行,由下游任务自行处理取消逻辑,下游自己写select <- ctx.Done()的逻辑。context是线程安全的,其本身是不可变的(immutable),且并发情况下会加锁。
参考
Last updated