context的作用
- context主要目的是跟踪goroutine调用树,在内部维护一个调用树,在调用树中传递退出通知和上下文数据
- 退出通知
- 当go服务处理一个请求时,可能需要开多个goroutine,形成goroutine树
- 例如一个去数据库拿数据,一个调用下游接口
- 这样就形成了一个树状结构
- 但是这个结构只是在程序员头脑中抽象出来的,runtime本身没有维护这样的树状结构
- 如果这个请求被取消了(例如刷新了浏览器或者超时了),那么下游goroutine都应该及时退出
- 因为他们的“工作成果”不再被需要了,如果不及时退出,可能会造成协程数量激增,内存耗尽
- 但是Go的goroutine 之间没有父与子的关系,也就没有所谓子进程退出后的通知机制
- 所以需要利用context关闭树状goroutine链
- 当go服务处理一个请求时,可能需要开多个goroutine,形成goroutine树
- 上下文传递
- 作用类似于goroutine local storage(GLS),用于在单个goroutine和子goroutine之间传递共有数据
- 应该只传递 告知性质 的东西,而不是 控制性质 的东西
- 如果发现你的函数在某些
Context.Value
下无法正确工作,那就说明这个信息不应该放在里面,而应该放在接口参数上
- 坏处
- 传递的是空接口,所以需要断言,开销较大
- 值在传递过程中可能被后续子context覆盖,且不易发觉
- 降低了程序的可读性,隐藏了期望的输入输出参数让接口定义更加模糊
- 常见传递数据类型
- 日志信息(request ID,user ID)
- 调试信息
- 作用类似于goroutine local storage(GLS),用于在单个goroutine和子goroutine之间传递共有数据
context的使用
- context应该作为函数的第一个参数
- 不知道传递什么context时不要传nil,应该传
context.TODO()
- 不知道传递什么context时不要传nil,应该传
- 不要把 Context 放在结构体中, 因为如果包含 Context 结构体的实例的生命周期贯穿多个任务, 容易出现bug
- 例如结构体中包含的 Context 在第一次任务的执行过程中被上游取消了,那么之后涉及该结构体值的第二次、第三次任务调用时,检查 Context 时都会认为该次任务被取消而直接返回错误
- Context接口定义了 4 个方法,它们都是幂等的
- 也就是说连续多次调用同一个方法,得到的结果都是相同的
Context.WithXXX
有可能会在内部创建一个goroutine,因此如果最后没有调用CancelFunc
关掉的话,会发生goroutine泄露WithCancel
一般是context被包裹在了结构体中才会新开goroutine监听父context的Done信号WithTimeout
和WithDeadline
是在使用time.AfterFunc
函数时隐式新开了goroutine
emptyCtx
Done()
如果返回的为nil
,则代表该 Context 是一个永远不会被取消的 emptyCtx- 此时
select
读取一个nil
的通道会永久阻塞,符合emptyCtx没有超时时间的语义
- 此时
cancelCtx
- 下游的goroutine 应该监听
Done()
返回的chan &#x