如果大家觉得文字版太枯燥,可以看一下我b站上分享的关于 golang context底层设计探究 ,全面详细,易懂,感谢大家观看,链接: Golang Context 底层源码拆解:3 个接口 + 4 个结构体全解析_哔哩哔哩_bilibili
Context 用于在多个函数、方法、协程、跨 API、进程之间传递信息。于 Go 1.7 版本发布,并纳入到 Go 语言标准库中。它不仅仅可以用于跨 API 场景传递上下文,同时还可以实现级联取消、超时控制等操作。
Context 是可以进行嵌套的,一个非顶级或末级的 Context 是拥有其父亲和一个或多个儿子,我们可以把它看作是一个树,也就是 Context 树
Go 官方提供了 三个接口的定义 和 四个结构体的实现
一、三个接口
1. Context接口:
规范了一个可取消,可携带数据,可传递错误的上下文对象,实现信息的 "生命周期" 和 "信息共享"
Go
// A Context carries a deadline, a cancellation signal, and other values across
// API boundaries.
//
// Context's methods may be called by multiple goroutines simultaneously.
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key any) any
} // context接口 源码
1)Deadline():表示Context实现要携带一个Deadline (截止时间),当到达截止时间后该Context会被取消
2) Done(): 一个通道,用于接收该 Context 需要被取消的信号
3) Err(): 用于返回 Context 被取消的原因
4) Value(): 返回在ctx中传递保存的上下文,用于层层传递数据,例如用户的身份等等
2. Stringer接口:
定义了一个而用于生成字符串的标准方式
Go
// 源碼
type stringer interface {
String() string
}
String() 会将 Context 生成一个字符串,让任何ctx实现都能被打印转成人类可读的字符串, 方便调试和日志记录,如果没实现,只能看到地址

打印上述语句,如果弄i的ctx实现了String() 你会看到:

如果没实现的话看到的就是一些地址等可读性不强的字符
3. Canceler接口:
定义了了一个可取消的上下文的规范要求。TimerCtx 和 CancelCtx因为可以进行取消,所以需要实现该接口
Go
// A canceler is a context type that can be canceled directly. The
// implementations are *cancelCtx and *timerCtx.
type canceler interface {
cancel(removeFromParent bool, err, cause error)
Done() <-chan struct{}
}
1)Cancel(removeFromParent bool,err,cause error)
触发上下文的取消操作,主要功能有
-
关闭 Done() 通道,通知所有监听该上下文的协程
-
标记上下文为[已取消] 状态;
-
可选从父上下文的子节点列表中移除自己
-
记录取消的错误信息和根因
2)Done() <-chan struct{}
返回一个只读的 struct{} 通道,用于监听上下文的[取消信号] ,上下文被取消(调用Cancel()) 或超时时,该通道会被关闭,协程可通过监听该通道,实现退出
二、四个结构体
1. EmptyCtx
用于表示空上下文的类型,作为 Context链中的默认的顶级上下文,实现了下面两个接口,如图所示:Stringer和Context
Go
// 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
}
可以看出源码对ctx接口的实现都为空,本质上就是实现了一个空的结构体
2. ValueCtx:
携带了一个键值对,实现了 Context 接口的 Value 方法
实现了Stringer 和 Context两个接口:

Go
type valueCtx struct {
Context
key, val any
}
func WithValue(parent Context, key, val any) Context {
if parent == nil {
panic("cannot create context from nil parent")
}
if key == nil {
panic("nil key")
}
if !reflectlite.TypeOf(key).Comparable() { // 判断该 key 是否是可比较的
panic("key is not comparable")
}
return &valueCtx{parent, key, val}
}
// 递归遍历 Context 链获取 key 对应的 value
func (c *valueCtx) Value(key any) any {
if c.key == key { // 当前节点就名中了,直接返回值
return c.val
}
return value(c.Context, key) // 没命中,向上递归
}
func value(c Context, key any) any {
for { // 无线循环,一直往上找
switch ctx := c.(type) { // 类型断言,查看当前节点是什么品种
case *valueCtx:
if key == ctx.key {
return ctx.val
}
c = ctx.Context
case *cancelCtx:
if key == &cancelCtxKey {
return c
}
c = ctx.Context
case withoutCancelCtx:
if key == &cancelCtxKey {
// This implements Cause(ctx) == nil
// when ctx is created using WithoutCancel. return nil
}
c = ctx.c
case *timerCtx:
if key == &cancelCtxKey {
return &ctx.cancelCtx
}
c = ctx.Context
case backgroundCtx, todoCtx: // 如果爬到最后还没有找到,就返回nil
return nil
default:
return c.Value(key)
}
}
}
// 从当前节点逐级向上寻找,遇到第一个匹配的key立即返回,爬到顶部还没找到就返回nil
3. CancelCtx:
cancelCtx 表示可取消的上下文。当 cancelCtx 被取消时,它也会取消任何 实现了 canceler 接口的所有子孙。
实现了Cancel,Stringer,Context三个接口:

Go
type cancelCtx struct {
Context // 嵌入,继承了父亲的所有方法
mu sync.Mutex // 锁,保护下面字段
done atomic.Value // chan struct{} 的值,懒加载创建,由第一次取消时调用关闭
children map[canceler]struct{} // 由第一次撤销调用设置为 nil
err error // 保存上下文取消时用户设置的 error,给外部看的取消圆心,
cause error // Go 1.20+ 新增,记录取消的「根因」(可通过 `context.Cause()` 获取),给调试日志看的原因
}
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
c := withCancel(parent)
return c, func() { c.cancel(true, Canceled, nil) }
}
func withCancel(parent Context) *cancelCtx {
if parent == nil {
panic("cannot create context from nil parent")
}
c := newCancelCtx(parent)
propagateCancel(parent, c)
return c
}
// 实现Done方法,外部用 `select { case <-ctx.Done(): ... }` 就是读的它。
func (c *cancelCtx) Done() <-chan struct{} {
// 线程安全的读第一次
d := c.done.Load()
if d != nil {
// 已存在就直接返回
return d.(chan struct{})
}
// 不存在的话就枷锁创建,并发环境下可能多个协程同时第一次访问,加互斥锁保护
c.mu.Lock()
defer c.mu.Unlock()
// 再读一次,仍未空才真正make(chan),然后原子写入,保证只创建一次
d = c.done.Load()
if d == nil {
// 懒加载,如果没有懒加载,在每次 WithCancel 的时候就立即创建,占内存,约等于32b,如果一条context链路上又1000个中间件节点,就浪费了32kb,而且有99%的节点永远不会被监听,而懒加载就是直到有人第一次调用Done()时才会创建
d = make(chan struct{})
c.done.Store(d)
}
return d.(chan struct{})
}
1. 这里为什么要持有锁 sync.Mutex?
1. 对于 `children map[canceler]struct{}` 字段
- 在并发情况下,多个 Goroutine 都需要对 进行读写,map 本身并发不安全,并发修改会导致 panic 。
- `children[c] = struct{}{}`、`delete(children, c)` 这样的复合操作需要保证原子性,在并发下如果被打断,对导致 panic。
2. `err`/`cause` 赋值
`err`/`cause` 是普通 error 类型,赋值非原子操作
需保证「仅第一次取消时赋值」(幂等性),锁能防止并发赋值覆盖
3. `done` 通道的创建(懒加载)
`done` 是懒加载的(第一次调用 `Done()` 时创建),锁能防止多个协程并发创建多个 `done` 通道(保证唯一性)。
4. `Cancel` 方法的幂等性
锁能保证「仅第一次调用 `Cancel` 时执行取消逻辑」,后续调用直接返回,避免重复关闭 `done` 通道(关闭已关闭的 chan 会 panic)。
2. 为什么 `done` 要使用原子类型 `atomic.Value`?
`done` 字段不使用锁来保护(只在懒加载时使用锁),是为了提高性能,不用每次调用 `Done()` 都需要加一次锁。
4. TimerCtx:
实现了String和Context两个接口:

用于携带定时器和截止时间信息。它会嵌入一个 cancelCtx,将 Done 和 Err 方法委托给 cancelCtx。同时,它通过停止定时器后委托给 cancelCtx.cancel 方法来实现取消功能。
Go
type timerCtx struct {
*cancelCtx // 嵌入此字段就相当于有了Done/Err/cancel等方法
timer *time.Timer // 定时器。到时间自动调用 cancel
deadline time.Time // 截止时间,最晚什么时候到期
}
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
// context不能凭空长出来
if parent == nil {
panic("cannot create context from nil parent")
}
// 如果你的父节点本身就比你更早到期,就不用设定时器了
if cur, ok := parent.Deadline(); ok && cur.Before(d) {
return WithCancel(parent)
}
// 新建节点
c := &timerCtx{
cancelCtx: newCancelCtx(parent),
deadline: d,
}
// 把自己挂到父节点下面,同时检查父节点是不是被取消,如果是,立即把c取消掉,保持父死子随
propagateCancel(parent, c)
// 检查是否过期,过期直接取消
dur := time.Until(d)
if dur <= 0 {
c.cancel(true, DeadlineExceeded, nil)
return c, func() { c.cancel(false, Canceled, nil) }
}
c.mu.Lock()
defer c.mu.Unlock()
if c.err == nil {
// 启动定时器,用户可随时 calcel() 提前结束
c.timer = time.AfterFunc(dur, func() {
c.cancel(true, DeadlineExceeded, nil)
})
}
// 确保用户任何时候手动执行cancel()都能提前结束
return c, func() { c.cancel(true, Canceled, nil) }
}
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
}
其保存了一个定时器和截止时间信息。
并借助 cancelCtx 来实现取消操作。当取消 timerCtx 时,它会首先停止定时器,然后委托给 cancelCtx.cancel 方法来执行取消操作,以确保整个上下文树能够正确地被取消。然后可以看到,官方提供了两个函数来创建 TimerCtx,分别是 `WithTimeout()` 和 `WithDeadline()`
以上就是四个结构体实现对应的接口的方法,感谢大家观看!有问题欢迎在评论区讨论或者私信!
