当前位置: 首页 > news >正文

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

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时候,管道关闭,发送通知,就取消了当前goroutinefmt.Println("任务被取消")returndefault: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() errorValue(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 {Contextmu       sync.Mutex            // protects following fieldsdone     atomic.Value          // of chan struct{}, created lazily, closed by first cancel callchildren map[canceler]struct{} // set to nil by the first cancel callerr      error                 // set to non-nil by the first cancel callcause    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 的父 Contextc.Context = parent// 获取 parent 的 Done channel,用于监听是否被取消done := parent.Done()if done == nil {// 如果 parent 永远不会被取消(比如 Background() 或 TODO()),则直接返回// parent不需要children,所以child不用找parentreturn}// 非阻塞检查:如果 parent 已经被取消select {case <-done:// parent 已经被取消,立即取消 childchild.cancel(false, parent.Err(), Cause(parent))returndefault:// parent 还没被取消,继续后续逻辑}// 尝试将父context转换为*cancelCtx类型(或它的派生类型)// 是不是有点奇怪传进来的ctx不就是这个类型吗?为什么还要再检查呢?非也,propagateCancel方法// 在很多处都有使用,传进来的parent不一定都是*cancelCtx类型if p, ok := parentCancelCtx(parent); ok {// 如果 parent 是 *cancelCtx 或者基于 *cancelCtx 派生的p.mu.Lock()if p.err != nil {// 如果 parent 已经被取消,则立刻取消 childchild.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 取消时,能级联取消子 Contextchild.cancel(false, parent.Err(), Cause(parent))})// 用 stopCtx 包装 parent,便于后续调用 stop() 移除回调c.Context = stopCtx{// 将当前 *cancelCtx 的直接父 Context 设置为 stopCtxContext: parent, // stopCtx 的父 Context 设置为 parentstop:    stop,}c.mu.Unlock()return}// 如果 parent 既不是 *cancelCtx,也没实现 afterFuncer// 那就只能启一个 goroutine 去监听 parent.Done()goroutines.Add(1)go func() {select {case <-parent.Done():// parent 被取消时,取消 childchild.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 作为 causeif cause == nil {cause = err}c.mu.Lock()// 如果 c.err 已经有值,说明当前 context 已经被取消过了if c.err != nil {c.mu.Unlock()return // 避免重复取消}// 设置取消原因c.err = errc.cause = cause// 获取 done channeld, _ := 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() 的 goroutineclose(d)}// 遍历所有子 context,递归取消// 这里也就是 propagateCancel 方法建立联系的用处for child := range c.children {// 注意:这里在持有 parent 的锁的情况下调用 child.cancel// 这是 Go context 源码中的一个经典点,允许这种锁的嵌套调用child.cancel(false, err, cause)}// 清空 children map,释放引用,避免内存泄漏c.children = nilc.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 channeldone := 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 channelpdone, _ := p.done.Load().(chan struct{})// 核心检查:如果 parent.Done() 返回的 channel != *cancelCtx.done// 说明 parent 被包装过(比如自定义了一个不同的 Done channel)// 这种情况下我们不能“越过包装”直接操作内部 cancelCtxif 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,就可以直接返回creturn 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的类型转化后去到对应的caseswitch ctx := c.(type) {case *valueCtx:if key == ctx.key {return ctx.val}c = ctx.Contextcase *cancelCtx:if key == &cancelCtxKey {return c}c = ctx.Contextcase withoutCancelCtx:if key == &cancelCtxKey {// This implements Cause(ctx) == nil// when ctx is created using WithoutCancel.return nil}c = ctx.ccase *timerCtx:if key == &cancelCtxKey {return &ctx.cancelCtx}c = ctx.Contextcase backgroundCtx, todoCtx:return nildefault: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),返回一个 *cancelCtxc := 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 {cancelCtxtimer *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)returndefault: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)}// 创建一个带有截止时间的 timerCtxc := &timerCtx{deadline: d,}// 建立父子关系,让父 Context 取消时能级联取消当前 cc.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 到期时自动触发 cancelc.mu.Lock()defer c.mu.Unlock()if c.err == nil { // 还没有被取消c.timer = time.AfterFunc(dur, func() {// 所以最底层是依赖于time.AfterFunc这个方法的// 到时间时触发取消,并写入 causec.cancel(true, DeadlineExceeded, cause)})}// 返回 Context 和 cancel 函数// 注意:手动调用 cancel() 时,原因总是 context.Canceled,而不是 causereturn 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,否则直接 panicpanic("不能从 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 {Contextkey, 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的类型转化后去到对应的caseswitch 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.Contextcase withoutCancelCtx:if key == &cancelCtxKey {// This implements Cause(ctx) == nil// when ctx is created using WithoutCancel.return nil}c = ctx.ccase *timerCtx:if key == &cancelCtxKey {return &ctx.cancelCtx}c = ctx.Contextcase backgroundCtx, todoCtx:return nildefault:return c.Value(key)}}
}

参考:

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


文章转载自:

http://zPFlj38O.gwdmj.cn
http://wTImktJX.gwdmj.cn
http://AJQIncbj.gwdmj.cn
http://jqykN5pG.gwdmj.cn
http://7F45ImxT.gwdmj.cn
http://CI6PJ5cF.gwdmj.cn
http://6JP44Ct7.gwdmj.cn
http://VOgtgBcB.gwdmj.cn
http://1lYxrcV9.gwdmj.cn
http://A3jsW8O5.gwdmj.cn
http://Iry65Vc2.gwdmj.cn
http://SR8qSnn3.gwdmj.cn
http://lVfg9kOp.gwdmj.cn
http://ZdwTwrse.gwdmj.cn
http://Ip6TTRzr.gwdmj.cn
http://NODpdE13.gwdmj.cn
http://8Mq8u6jH.gwdmj.cn
http://p3O95DKD.gwdmj.cn
http://0ynmIQ5Q.gwdmj.cn
http://UNYIicg4.gwdmj.cn
http://YAnncEFZ.gwdmj.cn
http://lJ8mVirY.gwdmj.cn
http://oH9xtyEQ.gwdmj.cn
http://WtFGSph0.gwdmj.cn
http://74V4wcrw.gwdmj.cn
http://zTKXJ4G8.gwdmj.cn
http://qcmOL8e4.gwdmj.cn
http://Ak3KY5iC.gwdmj.cn
http://65p4eAj1.gwdmj.cn
http://pSd3alJv.gwdmj.cn
http://www.dtcms.com/a/368724.html

相关文章:

  • 【JMeter】分布式集群压测
  • GEO 搜索引擎优化系统源码搭建与定制开发,支持OEM
  • Linux学习-硬件(串口通信)
  • 【蓝桥杯选拔赛真题65】C++求个数 第十四届蓝桥杯青少年创意编程大赛 算法思维 C++编程选拔赛真题解
  • AI美颜与瘦脸技术全解析
  • Dify on DMS,快速构建开箱即用的客服对话数据质检服务
  • 数字人打断对话的逻辑
  • Claude Code成本浪费严重?80%开支可省!Token黑洞解密与三层省钱攻略
  • 基于STM32的交通灯设计—紧急模式、可调时间
  • (未完待续...)如何编写一个用于构建python web项目镜像的dockerfile文件
  • OpenResty 和 Nginx 到底有啥区别?你真的了解吗!
  • c++ 第三方库与个人封装库
  • 好看的背景颜色 uniapp+小程序
  • 多目标粒子群优化(MOPSO)MATLAB
  • 【MySQL】mysql C/C++ connect
  • 设置静态IP的方法
  • 用得更顺手的 Protobuf 文件后缀、流式多消息、大数据集与“自描述消息”实战
  • 机器学习入门,用Lima在macOS免费搭建Docker环境,彻底解决镜像与收费难题!
  • 从碎片化到一体化:Java分布式缓存的“三级跳”实战
  • 深入剖析RocketMQ分布式消息架构:从入门到精通的技术全景解析
  • 通过API接口管理企业微信通讯录案例
  • 飞算JavaAI炫技赛:电商系统开发全流程实战解析
  • MySQL集群——主从复制
  • 项目必备流程图,类图,E-R图实例速通
  • 苹果 AI 探秘:代号 “AFM” —— “温柔的反叛者”
  • CAN通信入门
  • 1分钟了解等保测评流程
  • 【GEOS-Chem模型第三期】使用 Spack 构建 GEOS-Chem 等
  • 【Linux手册】动静态库:从原理到制作
  • 嵌入式ARM64 基于RK3588原生SDK添加用户配置选项build lunch