2726 字
14 分钟
Golang context 包实现详解

context#

context用于在 goroutine 间传递特定数据、取消信号等上下文信息。一般用于服务器中的请求链路中,实际应用示例不在本文赘述。

本文基于 Go 1.24.5 的源码,从实现角度逐层分析 context 的结构设计与取消机制。

context 接口定义#

Context 是一个抽象接口,定义了取消、超时和键值传递的统一语义,所有具体类型(如 cancelCtxtimerCtx)都是它的实现。

Context interface
├── emptyCtx
│   ├── backgroundCtx
│   └── todoCtx
├── cancelCtx
│   └── timerCtx
└── valueCtx
type Context interface {
// Deadline 返回 context 被取消的时间
// 返回 false 代表无 deadline,不会自动取消
Deadline() (deadline time.Time, ok bool)
// Done 返回一个 channel,当 context 被取消,即工作完成时,会关闭这个 channel
// 若 context 不会被取消,Done 返回 nil
// Done 关闭可能会与 cancel 函数的返回异步发生
Done() <-chan struct{}
// 在 Done 被关闭后,返回 context 的取消原因
Err() error
// Value 返回 context 中对应键中存储的值,若键不存在则返回 nil
Value(key any) any
}

emptyCtx: TODO/Background#

emptyCtx 是最简单的 context接口实现,永不取消,不存储键值对,无 deadline。

// backgroundCtx 与 todoCtx 在实现上都是 emptyCtx,仅有 String() 一处不同
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 用于创建最顶层 context
func Background() Context {
return backgroundCtx{}
}
// TODO 用于不确定需要使用何种 context 时作为占位符
func TODO() Context {
return todoCtx{}
}

WithCancel#

WithCancelcontext 的核心构造函数之一,它返回一个可手动取消的 context。其底层依赖 cancelCtx 来实现取消传播。cancelCtx是带 cancel 功能的 context,在调用 cancel() 时也会同时取消其子 context

cancelCtx#

cancelCtx中嵌套了一个 Context的匿名字段,这个字段代表其父节点。同时,cancelCtx本身也实现了Context接口。

type cancelCtx struct {
    Context // 嵌入其父 ctx
    mu sync.Mutex // 互斥锁,保证线程安全
    done atomic.Value // 存储一个 chan struct{}, 懒加载,在调用 cancel() 时关闭
    children map[canceler]struct{} // 子 ctx 的集合,在 cancel() 被调用时设为 nil
    err error // 在 cancel() 被调用时设置
    cause error // 取消原因
}
// cancelCtxKey 是一个 int 指针类型的全局变量,用于判断当前 ctx 是否为 cancelCtx类型
// 返回最早的 cancelCtx
func (c *cancelCtx) Value(key any) any {
// 返回自身
if key == &cancelCtxKey {
return c
}
return value(c.Context, key)
}
// 当找不到 &cancelCtxKey 时会继续调用 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 {
return nil
}
c = ctx.c
case *timerCtx:
if key == &cancelCtxKey {
return &ctx.cancelCtx
}
c = ctx.Context
case backgroundCtx, todoCtx:
return nil
default:
return c.Value(key)
}
}
}
// 懒创建并返回 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()
d = c.done.Load()
if d == nil {
d = make(chan struct{})
c.done.Store(d)
}
return d.(chan struct{})
}
func (c *cancelCtx) Err() error {
c.mu.Lock()
err := c.err
c.mu.Unlock()
return err
}

cancelCtx 不仅实现了 Context,同时还实现了 canceler接口,代表可取消的 ctx:

// cancelCtx 和 timerCtx 都实现了 canceler 接口
type canceler interface {
cancel(removeFromParent bool, err, cause error)
Done() <-chan struct{}
}

在 children 中存储的就是当前 cancelCtx 的最近的子 cancelCtx

在创建 cancelCtx的同时,定义 cancel函数,代码如下:

// WithCancel 返回一个带有新 Done channel 的派生 context
// 当父 context 被取消或当前 context 被取消时,Done 都会被关闭
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 := &cancelCtx{}
c.propagateCancel(parent, c) // 建立父子关系
return c
}

cancel()#

cancel() 会关闭自身的 done,设置 cause,递归通知所有子 ctx,然后将当前的 ctx 从父节点中移除。

// 关闭 c.done,取消 c 的所有 children
// 若 removeFromParent 为 true, 把 c 从它父节点的 children 中移除
func (c *cancelCtx) cancel(removeFromParent bool, err, cause error) {
if err == nil {
panic("context: internal error: missing cancel error")
}
if cause == nil {
cause = err // cause 未设置,取 err
}
c.mu.Lock()
if c.err != nil {
c.mu.Unlock()
return // context 已取消
}
c.err = err
c.cause = cause
d, _ := c.done.Load().(chan struct{})
if d == nil {
c.done.Store(closedchan) // 存 closedchan 作为已取消的标志
} else {
close(d) // 如果 done 已被设置,直接关闭 d
}
// 递归取消所有的子 context
for child := range c.children {
child.cancel(false, err, cause)
}
c.children = nil
c.mu.Unlock()
if removeFromParent { // 移除当前子 context
removeChild(c.Context, c)
}
}

removeChild()#

removeChild() 的作用是寻找 parentcancelCtx 类型的祖先 p,并维护 pchildren

func removeChild(parent Context, child canceler) {
if s, ok := parent.(stopCtx); ok { // 解绑回调函数
s.stop()
return
}
p, ok := parentCancelCtx(parent)
if !ok {
return
}
p.mu.Lock()
if p.children != nil {
delete(p.children, child) // 在祖先 ctx 的 children 中删除 child
}
p.mu.Unlock()
}

parentCancelCtx()#

一直向上寻找最近的 cancelCtx 祖先,检验后返回。

func parentCancelCtx(parent Context) (*cancelCtx, bool) {
done := parent.Done()
if done == closedchan || done == nil {
return nil, false
}
     p, ok := parent.Value(&cancelCtxKey).(*cancelCtx)
     if !ok {
         return nil, false
     }
     pdone, _ := p.done.Load().(chan struct{})
     if pdone != done {
         return nil, false
     }
     return p, true
}

propagateCancel#

propagateCancel的作用一言以蔽之就是将 parent 注册为 c 的父节点,维护祖先 cancelCtx 的 children,将 c 放入到 children 中,形成父链;如果没有找到祖先,则另起额外的协程监听 c 的取消。

func (c *cancelCtx) propagateCancel(parent Context, child canceler) {
c.Context = parent
done := parent.Done()
if done == nil {
return // 父 context 的 done 为 nil,不会 cancel
}
select {
case <-done:
// 父 context 已经被取消,接着取消 child
child.cancel(false, parent.Err(), Cause(parent))
return
default:
}
if p, ok := parentCancelCtx(parent); ok {
// 若祖先链路上存在 *cancelCtx p
p.mu.Lock()
if p.err != nil {
// p 已经被取消
child.cancel(false, p.err, p.cause)
} else {
// 将当前 cancelCtx 注册为 p 的子 cancelCtx
// 也就是说,cancelCtx 单独维护一条链,记录 cancelCtx 间的祖先关系,以此减少遍历经过的节点数
if p.children == nil {
p.children = make(map[canceler]struct{})
}
p.children[child] = struct{}{}
}
p.mu.Unlock()
return
}
if a, ok := parent.(afterFuncer); ok {
// 如果父 context 实现了 AfterFunc 方法
c.mu.Lock()
// 为这个父 context a 注册一个 afterFunc
stop := a.AfterFunc(func() {
child.cancel(false, parent.Err(), Cause(parent))
})
// 中间插入一个 stopCtx 节点
c.Context = stopCtx{
Context: parent,
stop: stop,
}
c.mu.Unlock()
return
}
// 起一个协程监听父 context 是否被取消,以判断取消 child
goroutines.Add(1)
go func() {
select {
case <-parent.Done():
child.cancel(false, parent.Err(), Cause(parent))
case <-child.Done():
}
}()
}

afterFuncCtx/stopCtx#

// AfterFunc 是 Go 1.21 引入的方法,作用是在 context 被取消时调用函数 f
// 返回的 stop 函数可以终止 ctx 和 f 的关联
func AfterFunc(ctx Context, f func()) (stop func() bool) {
a := &afterFuncCtx{
f: f,
}
a.cancelCtx.propagateCancel(ctx, a)
return func() bool {
stopped := false
a.once.Do(func() {
stopped = true
})
if stopped {
a.cancel(true, Canceled, nil)
}
return stopped
}
}
type afterFuncer interface {
AfterFunc(func()) func() bool
}
type afterFuncCtx struct {
cancelCtx
once sync.Once
f func()
}
func (a *afterFuncCtx) cancel(removeFromParent bool, err, cause error) {
a.cancelCtx.cancel(false, err, cause)
if removeFromParent {
removeChild(a.Context, a)
}
a.once.Do(func() {
go a.f()
})
}
// stopCtx 只用于在 cancelCtx 祖先链上无 cancelCtx 但存在 afterFunc 时,充当该 ctx 的父节点
// 调用 stop() 后,再取消 ctx 不会再执行 AfterFunc 注册的 f
type stopCtx struct {
Context
stop func() bool
}

WithCancelCause#

WithCancelCause 类似 WithCancel,不同在于返回值是 CancelCauseFunc 而不是 CancelFunc

// CancelCauseFunc 相比 CancelFunc 增加了一个取消原因,使用时需要传入一个 error 参数
// 可以调用 Cause 来获取具体的原因
type CancelCauseFunc func(cause error)
func WithCancelCause(parent Context) (ctx Context, cancel CancelCauseFunc) {
c := withCancel(parent)
return c, func(cause error) { c.cancel(true, Canceled, cause) }
}
// 如果 CancelCauseFunc(err) 设置过取消原因,则返回这个原因
// 否则返回 c.Err()
func Cause(c Context) error {
if cc, ok := c.Value(&cancelCtxKey).(*cancelCtx); ok {
cc.mu.Lock()
defer cc.mu.Unlock()
return cc.cause
}
return c.Err()
}

WithoutCancel#

WithoutCancel 返回一个 withoutCancelCtx,它不会继承父节点的取消语义。除了 Value()String() 以外其他方法行为与 emptyCtx 相同。

//
func WithoutCancel(parent Context) Context {
if parent == nil {
panic("cannot create context from nil parent")
}
return withoutCancelCtx{parent}
}
type withoutCancelCtx struct {
c Context
}
func (withoutCancelCtx) Deadline() (deadline time.Time, ok bool) {
return
}
func (withoutCancelCtx) Done() <-chan struct{} {
return nil
}
func (withoutCancelCtx) Err() error {
return nil
}
func (c withoutCancelCtx) Value(key any) any {
return value(c, key)
}
func (c withoutCancelCtx) String() string {
return contextName(c.c) + ".WithoutCancel"
}

timerCtx#

timerCtx 内部嵌入了一个匿名字段 cancelCtx。与 cancelCtx 中嵌入的 Context 字段不同,这里的嵌入并不是为了保存父节点,而更像是一种面向对象意义上的“继承”——timerCtx 通过组合 cancelCtx 来复用其取消逻辑与状态字段。真正的父节点并不保存在 timerCtx 本身,而是位于其内部的 cancelCtx.Context 字段中,也就是说,c.cancelCtx.Context 才是 timerCtx c 对应的父 context

// 当触发取消时,timerCtx 会先停止自身的定时器,然后将取消操作委托给内部的 cancelCtx.cancel()
type timerCtx struct {
cancelCtx
timer *time.Timer // 使用 cancelCtx.mu
deadline time.Time
}
func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
return c.deadline, true
}
func (c *timerCtx) String() string {
return contextName(c.cancelCtx.Context) + ".WithDeadline(" +
c.deadline.String() + " [" +
time.Until(c.deadline).String() + "])"
}
func (c *timerCtx) cancel(removeFromParent bool, err, cause error) {
c.cancelCtx.cancel(false, err, cause)
if removeFromParent {
removeChild(c.cancelCtx.Context, c)
}
c.mu.Lock()
if c.timer != nil {
c.timer.Stop()
c.timer = nil
}
c.mu.Unlock()
}

WithDeadline()#

在创建timerCtx时,通过time.AfterFunc 注册一个定时回调来触发取消。除了另外需要做一些时间逻辑判断其他实现与 cancelCtx 一致。

func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
return WithDeadlineCause(parent, d, nil)
}
func WithDeadlineCause(parent Context, d time.Time, cause error) (Context, CancelFunc) {
if parent == nil {
panic("cannot create context from nil parent")
}
if cur, ok := parent.Deadline(); ok && cur.Before(d) {
// 如果父节点的 deadline 更早
return WithCancel(parent)
}
c := &timerCtx{
deadline: d,
}
c.cancelCtx.propagateCancel(parent, c)
dur := time.Until(d)
if dur <= 0 {
c.cancel(true, DeadlineExceeded, cause)
return c, func() { c.cancel(false, Canceled, nil) }
}
c.mu.Lock()
defer c.mu.Unlock()
if c.err.Load() == nil {
c.timer = time.AfterFunc(dur, func() {
c.cancel(true, DeadlineExceeded, cause)
})
}
return c, func() { c.cancel(true, Canceled, nil) }
}

WithTimeout()#

WithTimeout 内部计算 deadline = time.Now().Add(timeout),并委托给 WithDeadline创建timerCtx

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
}
func WithTimeoutCause(parent Context, timeout time.Duration, cause error) (Context, CancelFunc) {
return WithDeadlineCause(parent, time.Now().Add(timeout), cause)
}

WithValue()#

WithValue() 返回一个存储提供的键值对的 valueCtx ,提供的 key 必须为可比较的。

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() {
panic("key is not comparable")
}
return &valueCtx{parent, key, val}
}

valueCtx#

每个valueCtx携带一组键值对,和 cancelCtx一样,嵌套一个Context,但不同的是valueCtx只重写了Value()函数,其他函数仍使用 parent 的实现。

type valueCtx struct {
Context
key, val any
}
func (c *valueCtx) String() string {
return contextName(c.Context) + ".WithValue(" +
stringify(c.key) + ", " +
stringify(c.val) + ")"
}
func stringify(v any) string {
switch s := v.(type) {
case stringer:
return s.String()
case string:
return s
case nil:
return "<nil>"
}
return reflectlite.TypeOf(v).String()
}
func (c *valueCtx) Value(key any) any {
if c.key == key {
return c.val
}
return value(c.Context, key)
}
// 不断向上寻找直至找到 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 {
return nil
}
c = ctx.c
case *timerCtx:
if key == &cancelCtxKey {
return &ctx.cancelCtx
}
c = ctx.Context
case backgroundCtx, todoCtx:
return nil
default:
return c.Value(key)
}
}
}
Golang context 包实现详解
https://yomi.moe/posts/go-context/
作者
藍々
发布于
2025-09-09
许可协议
CC BY-NC-SA 4.0