一、 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存信息

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐