go资深之路笔记(一) Context
一、 Context 的正确使用与底层原理
1.结构体
type Context interface {// Deadline 返回此 Context 被取消的时间点。// 如果未设置截止时间,ok 为 false。Deadline() (deadline time.Time, ok bool)// Done 返回一个 channel。当 Context 被取消或超时后,此 channel 会被关闭。// 这是一个只读的 channel。关闭的 channel 仍然可以读取,会立即返回零值。// 通过 select 语句监听 Done channel 的关闭,是接收取消信号的关键。Done() <-chan struct{}// Err 返回 Context 为何被取消的错误。// 如果 Done channel 还未被关闭,返回 nil。// 如果已被关闭,返回非 nil 的错误解释原因:context.Canceled 或 context.DeadlineExceeded。Err() error// Value 允许 Context 携带请求域的数据。// 这些数据必须是安全的,以供多个 Goroutine 同时使用。// 应该用于传递非必要的、过渡性的数据,而非函数的可选参数。Value(key any) any
}
2.根与派生
创建根上下文
context.Background()
context.TODO()
派生(包装)现有上下文
WithCancel 是基础的可取消上下文包装,返回的cancel,调用后即可以立刻取消包装后的上下文,以及其派生上下文(不会影响到父上下文)
WithDeadline,WithTimeout是带截止时间/时间间隔的可取消上下文包装
WithValue 是包装消息,一般是一些贯穿全文的必要信息比如:用户id, token等
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key, val any) Context
3.取消上下文原理:
以 func WithCancel(parent Context) (ctx Context, cancel CancelFunc) 为例:
3.1封装的 ctx有方法 Done() <-chan struct{},调用cancel后会给 Done() 的通道发送消息。监听这个通道的其他协程收到消息就可以返回(一般通过 select case default)。
3.2 构建关联(propagateCancel):
这个是解决当前上下文取消后,子上下文也被取消的关键:
派生的上下文如果是 cancelctx, 那么他会检查父上下文是不是也是,如果是的话,会将自己加入到 父context的 childmap,当父上下文被取消后,会遍历childmap来取消子上下文。
(ps: 父子上下文指的是包装前后,而不是函数传递。)
4. 实践总结:
4.1 谁创建谁取消:
ctx, cancel :=WithCancel(context.Background() )
defer cancel() // 直接defer取消
4.2 Done chan 是只读的
4.3谨慎适用 WithValue,这个一般只传贯穿全文的必要信息比如:用户id, token等,具体的参数 通过函数参数传递
4.4 context对象是不可变的(不能从普通变成cancelctx),只能包装
4.5 context 的三大作用:取消信号,超时/截止时取消信号,WithValue存信息