Golang中的context包介绍及源码阅读

发布于:2025-09-06 ⋅ 阅读:(13) ⋅ 点赞:(0)

context

基于go1.23.10

功能介绍

context包是Go语言中用于管理请求范围数据、取消信号和超时的重要工具。它特别适用于跨API边界和多个goroutine之间传递请求相关的值、取消信号和截止时间。


context的实现的功能本质都可以使用for+select+chan实现:

比如取消的功能,本质是就是使用select+chan通知,然后return即可;

type MyContext struct {
    cancelChan chan struct{}
}

func NewMyContext() *MyContext {
    return &MyContext{cancelChan: make(chan struct{})}
}

func (c *MyContext) Cancel() {
    close(c.cancelChan) // 一旦关闭一个 channel,所有读取操作(<-chan)会立即成功,返回通道类型的“零值”。
}

func (c *MyContext) Done() <-chan struct{} {
    return c.cancelChan
}
ctx := NewMyContext()

go func() {
    for {
        select {
        case <-ctx.Done(): // 这里调用自己的管道,不取消的时候阻塞着,调用Cancel时候,管道关闭,发送通知,就取消了当前goroutine
            fmt.Println("任务被取消")
            return
        default:
            fmt.Println("工作中...")
            time.Sleep(500 * time.Millisecond)
        }
    }
}()

time.Sleep(2 * time.Second)
ctx.Cancel()

设计总览

Context包的结构图如下:

在这里插入图片描述

exported 指的是“导出的标识符”,也就是那些首字母大写的函数、类型、方法、变量或常量。exported 函数就是可以在包外使用的函数。

Context是一个接口,定义了四个方法:

type Context interface {
 Deadline() (deadline time.Time, ok bool)
 Done() <-chan struct{}
 Err() error
 Value(key interface{}) interface{}
}

有四个exported函数供使用

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

cancelCtx

// A cancelCtx can be canceled. When canceled, it also cancels any children
// that implement canceler.
type cancelCtx struct {
    Context

    mu       sync.Mutex            // protects following fields
    done     atomic.Value          // of chan struct{}, created lazily, closed by first cancel call
    children map[canceler]struct{} // set to nil by the first cancel call
    err      error                 // set to non-nil by the first cancel call
    cause    error                 // set to non-nil by the first cancel call
}

cancelCtx这个结构体,Context直接是内嵌在里面的。因此可以把 context 看作一条 带着格式样标记的链表
Go 的 context 包,其实就是一条单向链表(链式结构),每次调用 WithCancelWithTimeoutWithValue,都会生成一个新的 ctx 节点,把 parent 链接上去。

每个 ctx 都持有一个父节点(一般字段是 Context 或者 parent)。
查找 Value / Done / Deadline 时,都是从当前 ctx 往父节点一级级递归/循环查找。
最终会走到根结点 backgroundCtxtodoCtx,这两个返回空值,查找结束。

内嵌
装饰器模式的一种实现,内嵌接口,然后传入接口的实现对象,有点像Java中的继承与重写(不过还是有些理念的区别,就好像蝙蝠和鸟都会飞一样),下面的例子可以体现。


type Person interface {
	Age()
	Name()
}
type GG struct {
}

func (g GG) Age() {
	fmt.Println("gg实现了这个方法")
}
func (g GG) Name() {
	fmt.Println("gg写了name")
}

type MM struct {
	Person
}

func (c MM) Name() {
	fmt.Println("mm重写了name")
}

func TestName(t *testing.T) {
	mm := MM{Person: GG{}}
	mm.Age() // gg实现了这个方法
	mm.Name()// mm重写了name
}

cancelCtx除了实现Context这个接口,还实现了canceler这个接口:

// 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{}
}

cancelCtx这个结构体,是exported函数WithCancel的基石

WithCancel

主要核心的方法是propagateCancel

propagateCancel
// WithCancel returns a copy of parent with a new Done channel. The returned
// context's Done channel is closed when the returned cancel function is called
// or when the parent context's Done channel is closed, whichever happens first.
//
// Canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this [Context] complete.
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) // 建立父子级ctx之间的通信联系,有很多case需要考虑,不是单纯的把子ctx加到父ctx的children中即可
	return c
}
// propagateCancel 用来建立父子 Context 的取消传播关系。
// 当 parent 被取消时,child 也会被取消。
// 它会把 cancelCtx 的父 context 设置为 parent。
func (c *cancelCtx) propagateCancel(parent Context, child canceler) {
	// 设置当前 cancelCtx 的父 Context
	c.Context = parent

	// 获取 parent 的 Done channel,用于监听是否被取消
	done := parent.Done()
	if done == nil {
		// 如果 parent 永远不会被取消(比如 Background() 或 TODO()),则直接返回
         // parent不需要children,所以child不用找parent
		return
	}

	// 非阻塞检查:如果 parent 已经被取消
	select {
	case <-done:
		// parent 已经被取消,立即取消 child
		child.cancel(false, parent.Err(), Cause(parent))
		return
	default:
		// parent 还没被取消,继续后续逻辑
	}

	// 尝试将父context转换为*cancelCtx类型(或它的派生类型)
    // 是不是有点奇怪传进来的ctx不就是这个类型吗?为什么还要再检查呢?非也,propagateCancel方法
    // 在很多处都有使用,传进来的parent不一定都是*cancelCtx类型
	if p, ok := parentCancelCtx(parent); ok {
		// 如果 parent 是 *cancelCtx 或者基于 *cancelCtx 派生的
		p.mu.Lock()
		if p.err != nil {
			// 如果 parent 已经被取消,则立刻取消 child
			child.cancel(false, p.err, p.cause)
		} else {
			// 否则,parent 还没被取消
			// 把 child 注册到 parent 的 children 集合里
			// 这样当 parent 被取消时,会遍历 children 并取消
			if p.children == nil {
				p.children = make(map[canceler]struct{})
			}
			p.children[child] = struct{}{}
		}
		p.mu.Unlock()
		return
	}

     // NOTE: 父 Context 实现了 afterFuncer 接口,扩展功能,context包没把这个接口实现,可以自己扩展实现
     // 测试文件 afterfunc_test.go 中 *afterFuncCtx 实现了 afterFuncer 接口,是一个很好的样例
	if a, ok := parent.(afterFuncer); ok {
		c.mu.Lock()
		stop := a.AfterFunc(func() {// 注册子 Context 取消功能到父 Context,当父 Context 取消时,能级联取消子 Context
			child.cancel(false, parent.Err(), Cause(parent))
		})
		// 用 stopCtx 包装 parent,便于后续调用 stop() 移除回调
		c.Context = stopCtx{// 将当前 *cancelCtx 的直接父 Context 设置为 stopCtx
			Context: parent, // stopCtx 的父 Context 设置为 parent
			stop:    stop,
		}
		c.mu.Unlock()
		return
	}

	// 如果 parent 既不是 *cancelCtx,也没实现 afterFuncer
	// 那就只能启一个 goroutine 去监听 parent.Done()
	goroutines.Add(1)
	go func() {
		select {
		case <-parent.Done():
			// parent 被取消时,取消 child
			child.cancel(false, parent.Err(), Cause(parent))
		case <-child.Done():
			// child 自己先被取消了,就直接退出 goroutine
		}
	}()
}


propagateCancel是一个很关键的方法,主要是用于建立parent与child的联系,在func withCancel(parent Context) *cancelCtx可以知道,parent就是传入的ctx,而child是新声明的ctx,是为了把这个新的ctx和传入的ctx建立联系。
详细说明:
propagateCancel() 方法将 cancelCtx 对象向上传播挂载到父 context 的 children 属性集合中,这样当父 context 被取消时,子 context 也会被级联取消。这个方法逻辑稍微有点多,也是 context 包中最复杂的方法了,拿下它,后面的代码就都很简单了。
首先将 parent 参数记录到 cancelCtx.Context 属性中,作为父 context。接下来会对父 context 做各种判断,以此来决定如何处理子 context。
第 9 行通过 parent.Done() 拿到父 context 的 done channel,如果值为 nil,则说明父 context 没有取消功能,所以不必传播子 context 的取消功能到父 context。
第 17 行使用 select…case… 来监听 <-done 是否被关闭,如果已关闭,则说明父 context 已经被取消,那么直接调用 child.cancel() 取消子 context。因为 context 的取消功能是从上到下级联取消,所以父 context 被取消,那么子 context 也一定要取消。
如果父 context 尚未取消,则在第 29 行判断父 context 是否为 *cancelCtx 或者从 *cancelCtx 派生而来。如果是,则判断父 context 的 err 属性是否有值,有值则说明父 context 已经被取消,那么直接取消子 context;否则将子 context 加入到这个 *cancelCtx 类型的父 context 的 children 属性集合中。
如果父 context 不是 *cancelCtx 类型,在第 50 行判断父 context 是否实现了 afterFuncer 接口。如果实现了,则新建一个 stopCtx 作为当前 *cancelCtx 的父 context。
最终,如果之前对父 context 的判断都不成立,则开启一个新的 goroutine 来监听父 context 和子 context 的取消信号。如果父 context 被取消,则级联取消子 context;如果子 context 被取消,则直接退出 goroutine。
至此 propagateCancel() 方法的主要逻辑就梳理完了。

以上的详细说明引自 Go 并发控制:context 源码解读 - 知乎

cancel

propagateCancel中提及了cancel()这个方法,这也是很核心的一个方法,cancelCtx的实现如下:

// 这个函数用于取消当前context,并递归取消所有子context,具体操作和目的如下:
// 1. 关闭 c.done channel,表示当前 context 已被取消。
// 2. 递归取消所有子 context。
// 3. 如果 removeFromParent = true,则把自己从父 context 的 children 中移除。
// 4. 如果是第一次取消,则记录取消的原因(err/cause)。
func (c *cancelCtx) cancel(removeFromParent bool, err, cause error) {
	// err 不能为空,取消必须有原因(通常是 context.Canceled 或 context.DeadlineExceeded)
	if err == nil {
		panic("context: internal error: missing cancel error")
	}

	// 如果调用方没有传 cause,就默认用 err 作为 cause
	if cause == nil {
		cause = err
	}

	c.mu.Lock()
	// 如果 c.err 已经有值,说明当前 context 已经被取消过了
	if c.err != nil {
		c.mu.Unlock()
		return // 避免重复取消
	}

	// 设置取消原因
	c.err = err
	c.cause = cause

	// 获取 done channel
	d, _ := c.done.Load().(chan struct{})
	if d == nil {
		// 如果还没初始化,直接存入全局的 closedchan(已关闭的 channel 常量)
		// 这样后续所有 <-c.Done() 都会立即返回
		c.done.Store(closedchan)
        // 这里对应的情况是,还没有调用ctx.Done()的时候,就已经调用cancel()方法了
        // 为了保持语义一致性:只要调用了cancel()所有的channel都会关闭,哪怕现在没开后续会开的
        // 有了这个设计,无论何时调用 ctx.Done(),都会立即返回并 unblock。
	} else {
		// 否则关闭已存在的 channel,通知所有等待 <-c.Done() 的 goroutine
		close(d)
	}

	// 遍历所有子 context,递归取消
    // 这里也就是 propagateCancel 方法建立联系的用处
	for child := range c.children {
		// 注意:这里在持有 parent 的锁的情况下调用 child.cancel
		// 这是 Go context 源码中的一个经典点,允许这种锁的嵌套调用
		child.cancel(false, err, cause)
	}
	// 清空 children map,释放引用,避免内存泄漏
	c.children = nil

	c.mu.Unlock()

	// 如果需要从父节点移除自己
	if removeFromParent {
		removeChild(c.Context, c)
	}
}

上面讲到传入一个已经关闭的channel,这个设计和Done()方法是连在一起的:

func (c *cancelCtx) Done() <-chan struct{} {
	d := c.done.Load()
	if d != nil {// 这里发现已经存在channel了,就直接返回,如果是上面给到的closeedchan,那么在select中会直接命中
		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{})
}
parentCancelCtx

propagateCancel方法下面提到了parentCancelCtx方法,主要是用于判断是不是cancelCtx的实例,这是一个用的很多的内部方法:

// 这个函数用于从父context(即parent)中提取底层的*cancelCtx实例
// 具体:
//  1. 先通过 parent.Value(&cancelCtxKey) 找到离 parent 最近的 *cancelCtx;
//  2. 再检查 parent.Done() 是否和这个 *cancelCtx 的 done channel 一致。
//     如果一致,说明 parent 没被包装,可以直接操作 parent 的 *cancelCtx。
//     如果不一致,说明 parent 被包装过(例如自定义了 Done()),不能绕过包装层。
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
	// 拿到 parent 的 Done channel
	done := parent.Done()

	// 如果 Done() 是全局 closedchan(永远已关闭)或 nil(永远不会关闭)
	// 说明 parent 不会真正传播取消,不用继续
	if done == closedchan || done == nil {
		return nil, false
	}

	// 从 parent 的 Value() 中取出 *cancelCtx
	// cancelCtx实现Value方法时,把cancelCtxKey写死在方法里了,即如果传入的
    // 是cancelCtxKey的指针(cancelCtxKey是包变量int类型),就将cancelCtx的
    // 实现本身返回,断言一下就知道这个是不是cancelCtx类型了
	p, ok := parent.Value(&cancelCtxKey).(*cancelCtx)
	if !ok {
		// parent 根本不是 *cancelCtx,直接返回失败
		return nil, false
	}

	// 拿到这个 *cancelCtx 内部的 done channel
	pdone, _ := p.done.Load().(chan struct{})

	// 核心检查:如果 parent.Done() 返回的 channel != *cancelCtx.done
	// 说明 parent 被包装过(比如自定义了一个不同的 Done channel)
	// 这种情况下我们不能“越过包装”直接操作内部 cancelCtx
	if pdone != done {
		return nil, false
	}

	// 走到这里说明 parent 确实是 *cancelCtx,且没被包装,可以直接用
	return p, true
}

cancelCtx的Value

cancelCtx实现的Value如下:

// cancelCtx实现的Value配合parentCancelCtx中的判断,可以快速判断一个ctx是不是cancelCtx类型
func (c *cancelCtx) Value(key any) any {
	if key == &cancelCtxKey { // 这里可以看到,如果key是cancelCtxKey,就可以直接返回c
		return c
	}
	return value(c.Context, key)
}
// 相对的,valueCtx是没有这个逻辑的,这样就可以有区分了。
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 { // 这是一个for,用于c的类型转化后去到对应的case
		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:
			return nil
		default:
			return c.Value(key)
		}
	}
}

WithCancelCause

自定义err,和WithCancel基本一致,如果不用这个方法,返回的是系统默认err,就是context canceled。如果想自定义, 可以传一个,用context.Cause()获取自定义err即可。

// WithCancelCause 的作用:
// 类似于 WithCancel,但返回的是 CancelCauseFunc 而不是 CancelFunc。
// CancelCauseFunc 允许调用方传入一个 error(即 "cause"),用来记录取消的具体原因。
// 之后可以通过 context.Cause(ctx) 取回这个 error。
//
// 语义:
// - 调用 cancel(causeError) 时:
//     1. ctx.Err() 依旧返回 context.Canceled(保持向后兼容);
//     2. 但 context.Cause(ctx) 返回传入的 causeError。
// - 如果 cancel(nil),则会把 cause 设置为 context.Canceled。
//
// 使用示例:
//   ctx, cancel := context.WithCancelCause(parent)
//   cancel(myError)
//   ctx.Err()           // == context.Canceled
//   context.Cause(ctx)  // == myError
func WithCancelCause(parent Context) (ctx Context, cancel CancelCauseFunc) {
	// 实际内部还是调用 withCancel(parent),返回一个 *cancelCtx
	c := withCancel(parent)

	// 返回值:
	// 1. ctx:就是这个 *cancelCtx
	// 2. cancel:一个闭包,接收一个 error 作为 cause
	//    调用时执行 c.cancel(true, Canceled, cause)
	//    - 第一个参数 true 表示需要从父 context 的 children 集合中移除
	//    - 第二个参数固定传 context.Canceled(Err() 的表现始终一致)
	//    - 第三个参数就是调用方传入的 cause,会记录到 cancelCtx.cause 字段
	return c, func(cause error) { c.cancel(true, Canceled, cause) }
}
// 默认是这个err,context包固定的一个error
// Canceled is the error returned by [Context.Err] when the context is canceled.
var Canceled = errors.New("context canceled")

这是一个简单的测例:

func TestName5(t *testing.T) {
	bg := context.Background()
	//ctx, cancelFunc := context.WithCancel(bg)
	ctx, cancelFunc := context.WithCancelCause(bg)
	cancelFunc(errors.New("my error"))
	err := context.Cause(ctx)
	if err != nil {
		fmt.Println(err.Error())
	}
	err = ctx.Err()
	if err != nil {
		fmt.Println(err.Error())
		return
	}
}

timerCtx

基于cancelCtx的一个结构体,实现了Done and Err,可以通过停止timer来实现定时取消的功能。

// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to
// implement Done and Err. It implements cancel by stopping its timer then
// delegating to cancelCtx.cancel.
type timerCtx struct {
	cancelCtx
	timer *time.Timer // Under cancelCtx.mu.

	deadline time.Time
}

对此有四个方法是基于此结构体的:WithDeadline(), WithDeadlineCause(), WithTimeout(),WithTimeoutCause()

在这里插入图片描述
WithDeadlineCause是最底层的方法,另外三个都是对这个方法的封装,

WithTimeout (相对时间)
→ WithDeadline (绝对时间,无原因)
→ WithDeadlineCause (绝对时间,带原因)

从简单向复杂去讲,其中WithTimeout()使用起来最简单:

WithTimeout

这个函数是可以实现定时取消,不用手动调用返回的函数,一个例子:

bg := context.Background()
timeout, c := context.WithTimeout(bg, 5*time.Second)
defer c() // 如果直接调用c(),那么会直接取消

for {
    select {
    case <-timeout.Done(): // 会在5*time.Second后发送取消通知
        fmt.Println("end time")
        time.Sleep(time.Second)
        return
    default:
        time.Sleep(time.Second)
        fmt.Println("click")
    }
}

来看一下源码:

// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
//
// Canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this [Context] complete:
//
//	func slowOperationWithTimeout(ctx context.Context) (Result, error) {
//		ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
//		defer cancel()  // releases resources if slowOperation completes before timeout elapses
//		return slowOperation(ctx)
//	}
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
	return WithDeadline(parent, time.Now().Add(timeout)) // 可以看出WithTimeout就是一个WithDeadline简化版
}

这也是开发中常用的方法,将方法分层,底层的方法很灵活,但是使用较麻烦,对常用的功能进行简单的封装,方便开发。

WithDeadline
// WithDeadline 返回一个父 Context 的副本,并把它的截止时间调整为不晚于 d。
// 如果父 Context 的截止时间已经早于 d,
// 那么 WithDeadline(parent, d) 在语义上等价于 parent。
// 返回的 Context.Done channel 会在以下任一情况发生时关闭:
//   - 截止时间 d 到期
//   - 调用了返回的 cancel 函数
//   - 父 Context 的 Done channel 被关闭
// 以上三者以最先发生的为准。
//
// 取消这个 Context 会释放与之关联的资源,
// 因此在使用该 Context 的操作完成后,应尽快调用 cancel。
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
	return WithDeadlineCause(parent, d, nil)
}

功能遵从朴素的逻辑,没有什么特别的case,和方法名语义一致的方法。

WithDeadlineCause
// WithDeadlineCause 的行为类似于 [WithDeadline],
// 但当超过截止时间时,会把返回的 Context 的取消原因设置为 cause。
// 注意:返回的 [CancelFunc] 不会设置 cause,只会返回 context.Canceled。
func WithDeadlineCause(parent Context, d time.Time, cause error) (Context, CancelFunc) {
	if parent == nil {
		// 父 Context 不能为 nil,否则直接 panic
		// panic("父Context不能为nil")
        panic("cannot create context from nil parent")
	}

	// 检查父context是否已经有更早的截止时间
	if cur, ok := parent.Deadline(); ok && cur.Before(d) {
         // The current deadline is already sooner than the new one
         // 直接返回一个普通的可取消context,因为父context会更早触发取消
		return WithCancel(parent)
	}

	// 创建一个带有截止时间的 timerCtx
	c := &timerCtx{
		deadline: d,
	}

	// 建立父子关系,让父 Context 取消时能级联取消当前 c
	c.cancelCtx.propagateCancel(parent, c)

	// 计算当前时间到 d 的剩余时长
	dur := time.Until(d)
	if dur <= 0 {
		// 如果截止时间已经过去,立刻取消,并把 cause 作为取消原因
		c.cancel(true, DeadlineExceeded, cause)
		// 返回 Context,以及 cancel 函数(手动 cancel 时不会设置 cause)
		return c, func() { c.cancel(false, Canceled, nil) }
	}

	// 如果还没超时,设置定时器,在 d 到期时自动触发 cancel
	c.mu.Lock()
	defer c.mu.Unlock()
	if c.err == nil { // 还没有被取消
		c.timer = time.AfterFunc(dur, func() {// 所以最底层是依赖于time.AfterFunc这个方法的
			// 到时间时触发取消,并写入 cause
			c.cancel(true, DeadlineExceeded, cause)
		})
	}

	// 返回 Context 和 cancel 函数
	// 注意:手动调用 cancel() 时,原因总是 context.Canceled,而不是 cause
	return c, func() { c.cancel(true, Canceled, nil) }
}

withoutCancelCtx

// WithoutCancel 返回一个 parent 的副本,
// 但当 parent 被取消时,它不会跟着被取消。
// 换句话说,这个 Context 不会继承父 Context 的取消信号。
//
// 返回的 context 没有 Deadline 或 Err,Done channel 也永远是 nil。
// 调用 [Cause] 时,总是返回 nil。
func WithoutCancel(parent Context) Context {
	if parent == nil {
		// 父 Context 不能为 nil,否则直接 panic
		panic("不能从 nil 父 Context 创建 Context")
	}
	// 返回一个包装后的 withoutCancelCtx,
	// 它屏蔽了 parent 的取消、超时、错误等特性,
	// 但仍然可以从 parent 中获取 Value。
	return withoutCancelCtx{parent}
}

WithoutCancel - 取消隔离

  • 目的:阻断取消信号的传播,创建独立的context
  • 特点:继承父context的值但不受其取消影响
  • 使用场景:后台任务、日志记录、清理操作等需要独立生命周期的场景
// 用于创建不受父context取消影响的新context
ctx = context.WithoutCancel(parentCtx)

valueCtx

WithValue()是唯一可以用来传值的方法,而这个结构体就是方法WithValue()的基石,源码如下:

// A valueCtx carries a key-value pair. It implements Value for that key and
// delegates all other calls to the embedded Context.
type valueCtx struct {
	Context
	key, val any
}
// WithValue returns a copy of parent in which the value associated with key is
// val.
//
// Use context Values only for request-scoped data that transits processes and
// APIs, not for passing optional parameters to functions.
//
// The provided key must be comparable and should not be of type
// string or any other built-in type to avoid collisions between
// packages using context. Users of WithValue should define their own
// types for keys. To avoid allocating when assigning to an
// interface{}, context keys often have concrete type
// struct{}. Alternatively, exported context key variables' static
// type should be a pointer or interface.
//
// WithValue 返回父context的一个副本,其中关联key的值为val。
//
// context Values 仅应用于跨进程和API传递的请求范围数据,而不应用于向函数传递可选参数。
//
// 提供的key必须是可比较的,并且不应是string类型或任何其他内置类型,以避免使用context的包之间发生冲突。
// WithValue的用户应该为自己的key定义自定义类型。为了避免在分配给interface{}时分配内存,
// context key通常应具有具体类型struct{}。或者,导出的context key变量的静态类型应为指针或接口。
func WithValue(parent Context, key, val any) Context {
	// 参数校验:确保父context不为nil,避免创建无效的context链
	if parent == nil {
		panic("cannot create context from nil parent")
	}
	// 参数校验:key不能为nil,避免无法进行值查找
	if key == nil {
		panic("nil key")
	}
	// 参数校验:key必须是可比较的类型,因为context需要支持值的查找操作
	if !reflectlite.TypeOf(key).Comparable() {
		panic("key is not comparable")
	}
	
	// 创建并返回valueCtx实例,包装父context并存储键值对
	// valueCtx结构:{parent Context, key, val any}
	return &valueCtx{parent, key, val}
}

WithValue的逻辑是:父ctx放入的值,子context可以用;但反过来不行。可以看源码:

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 { // 这是一个for,用于c的类型转化后去到对应的case
		switch ctx := c.(type) {
		case *valueCtx:
			if key == ctx.key {
				return ctx.val
			}
			c = ctx.Context // 对于valueCtx而言,就是不断向上找值
		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:
			return nil
		default:
			return c.Value(key)
		}
	}
}


参考:

[1] https://segmentfault.com/a/1190000040917752#item-3
[2] Go 并发控制:context 源码解读 - 知乎


网站公告

今日签到

点亮在社区的每一天
去签到