核心结构

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

Context接口的四个方法主要有下列用途:

  1. Done方法和Err方法主要用于可Cancel的Context:type cancelCtx struct
  2. Value方法用于带值的Context:type valueCtx struct
  3. Deadline方法用于继承了cancelCtx的有deadline的Context:type timerCtx struct
  4. 最后,cancelCtx改写了Value方法,以使此方法可以判断自身或长辈(父辈、祖辈一直向上)是否为cancelCtx类型,与这个操作相关的是包变量var cancelCtxKey int

非零,空Context

Context包中定义了最简单的非零,空Context如下:

type emptyCtx int
注:
emptyCtx的定义处有注释如下:It is not struct{}, since vars of this type must have distinct addresses.
怎么理解emptyCtx不能定义为struct{}
因为当struct没有元素的时候,两个不同的zero-size变量在内存中可能有相同的地址,这就导致后面定义的两个不同的Context——backgroundtodo——可能有相同的内存地址
参考:
https://go.dev/ref/spec#Size_and_alignment_guarantees
https://cloud.tencent.com/developer/article/1918419

emptyCtx满足Context接口,几个方法的返回值也基本都是对应类型的零值

而由于emptyCtx本身并未被导出,因此Context基于此创建了两个不同的Context:

var (
    background = new(emptyCtx)
    todo       = new(emptyCtx)
)

这两个Context分别被函数BackgroundTODO导出

这两个Context都是以下特征:

  1. 非零,空
  2. 不被取消
  3. 没有值
  4. 没有deadline

其中:

  • background主要被用在main函数、初始化、测试等处,作为后续请求的顶级上下文
  • todo主要被用在暂不清楚使用哪种上下文,或者当前函数环境还为被扩展为可接收上下文时临时使用

valueCtx

valueCtx定义如下:

type valueCtx struct {
    Context
    key, val interface{}
}

除了父辈Context,新建一个valueCtx就带一个键值对

Value函数为

func (c *valueCtx) Value(key interface{}) interface{} {
    if c.key == key {
        return c.val
    }
    return c.Context.Value(key)
}

cancelCtx

cancelCtx定义如下:

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
}

其中canceler定义如下:

type canceler interface {
    cancel(removeFromParent bool, err error)
    Done() <-chan struct{}
}

cancelCtx的核心用法是:

  1. 查询最近的可cancel长辈(用到了Done方法,追溯到最近的cancelCtx类型长辈),没有就不做继承操作
  2. 如果有可cancel长辈,则将自己放进其Children的map中,并监听其Cancel信号进行自动cancel(注意,如果长辈是用户自定义的canceler,可能没有children map,则会额外启动一个goroutine来监听长辈的Done方法)
  3. 如果手动cancel被调用,则对自己的Children进行cancel调用,并判断是否需要将自己从长辈的children中移除

注:
上文提到使用cancelCtxKey和改写的Value方法获取cancelCtx长辈,就是在需要获取cancelCtx长辈时使用cancelCtxKey调用Value

parent.Value(&cancelCtxKey)

cancelCtxValue定义如下:

func (c *cancelCtx) Value(key interface{}) interface{} {
   if key == &cancelCtxKey {
       return c
   }
   return c.Context.Value(key)
}

区别于emptyCtxvalueCtxValue方法,其调用仅可能在cancelCtx长辈中获得返回值,于是达到了获取最近cancelCtx长辈的目的

timerCtx

timerCtx定义如下:

type timerCtx struct {
    cancelCtx
    timer *time.Timer // Under cancelCtx.mu.

    deadline time.Time
}

timerCtx继承cancelCtx,主要增加了计时器用来自动触发cancelCtxcancel方法

分享

最后分享一篇比较详细的Context分析文档:

https://juejin.cn/post/7108012893563535397


cainmusic
10 声望0 粉丝