我对 Context 包的理解
Go 的 context 包用于并发控制、超时管理和跨函数传递数据。本文深入讲解 context 的正确使用方式,避免常见的误用陷阱。
方法介绍
context 有两种创建方式 context.Background() 和 context.TODO()。这两个函数没有本质的区别,分析它们的源码可以发现都衍生自 emptyCtx:
// An emptyCtx is never canceled, has no values, and has no deadline.
// It is the common base of backgroundCtx and todoCtx.
type emptyCtx struct{}
func (emptyCtx) Deadline() (deadline time.Time, ok bool) {
return
}
func (emptyCtx) Done() <-chan struct{} {
return nil
}
func (emptyCtx) Err() error {
return nil
}
func (emptyCtx) Value(key any) any {
return nil
}
type backgroundCtx struct{ emptyCtx }
func (backgroundCtx) String() string {
return "context.Background"
}
type todoCtx struct{ emptyCtx }
func (todoCtx) String() string {
return "context.TODO"
}
// Background returns a non-nil, empty [Context]. It is never canceled, has no
// values, and has no deadline. It is typically used by the main function,
// initialization, and tests, and as the top-level Context for incoming
// requests.
func Background() Context {
return backgroundCtx{}
}
// TODO returns a non-nil, empty [Context]. Code should use context.TODO when
// it's unclear which Context to use or it is not yet available (because the
// surrounding function has not yet been extended to accept a Context
// parameter).
func TODO() Context {
return todoCtx{}
}
从源码中可以很清晰的看到 Background 建议在上下文最开始使用,所有其他上下文都应该从它衍生出来,TODO 建议在不确定使用什么类型上下文或在开发阶段(即使用的方法还没有被正式纳入生命周期管理时)使用。
使用上出初始化方法创建的上下文并没有任何功能,具体需要使用 context 包提供的 WithX 系列方法派生功能:
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key, val interface{}) Context
其数据结构均为组合了父 context 的对象:
基于一个父 Context 可以随意衍生,其实这就是一个 Context 树,树的每个节点都可以有任意多个子节点,节点层级可以有任意多个,每个子节点都依赖于其父节点。