go context包源码注解学习记录

79 阅读7分钟

1.context接口说明

type Context interface {
  //返回过期时间,在timerCtx中生效
  Deadline() (deadline time.Time, ok bool)
​
  //返回标识ctx是否结束的channel
  Done() <-chan struct{}
​
  //返回错误
  Err() error
​
  //返回ctx存放的key-value对
  Value(key any) any
}

在golang的context包中,有emptyCtx、cancelCtx、timerCtx、valueCtx都实现了上述context接口。

2.全局Err

//context被cancel时的错误
var Canceled = errors.New("context canceled")
​
//context超时时的错误
var DeadlineExceeded error = deadlineExceededError{}
​
type deadlineExceededError struct{}
​
func (deadlineExceededError) Error() string   { return "context deadline exceeded" }
func (deadlineExceededError) Timeout() bool   { return true }
func (deadlineExceededError) Temporary() bool { return true }

3.emptyCtx

1.emptyCtx是一个空context,本质是一个整型,context.Background()、context.TODO()均是返回此类型

type emptyCtx int//没有过期时间
func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
  return
}
​
//如果往chan写入/读取数据,均会阻塞
func (*emptyCtx) Done() <-chan struct{} {
  return nil
}
​
//没有err
func (*emptyCtx) Err() error {
  return nil
}
​
//没有value
func (*emptyCtx) Value(key any) any {
  return nil
}
​
func (e *emptyCtx) String() string {
  switch e {
  case background:
    return "context.Background"
  case todo:
    return "context.TODO"
  }
  return "unknown empty Context"
}

2.background、todo

var (
  background = new(emptyCtx)
  todo       = new(emptyCtx)
)
​
//返回一个非nil的emptyCtx,不会被cancel,没有value,没有deadline
//可用在main中,作为context链路中最顶层的父context
func Background() Context {
  return background
}
​
// TODO returns a non-nil, empty Context. Code should use context.TODO when
// it's unclear which Context to use or it is not yet available (because the
// surrounding function has not yet been extended to accept a Context
// parameter).
func TODO() Context {
  return todo
}

4.cancler 接口

//具备cancel能力,cancelCtx与timerCtx实现了此接口
type canceler interface {
  cancel(removeFromParent bool, err, cause error)
  Done() <-chan struct{}
}

5.cancelCtx

5.1 cancelCtx定义

//用于表示cancelCtx返回自身而约定的key
var cancelCtxKey inttype cancelCtx struct {
  Context                        // 继承context,作为当前cancelCtx的父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
}
​
func (c *cancelCtx) Value(key any) any {
  if key == &cancelCtxKey {
    return c
  }
  return value(c.Context, key)
}
​
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
}
​
type stringer interface {
  String() string
}
​
func contextName(c Context) string {
  if s, ok := c.(stringer); ok {
    return s.String()
  }
  return reflectlite.TypeOf(c).String()
}
​
func (c *cancelCtx) String() string {
  return contextName(c.Context) + ".WithCancel"
}
​
// cancel closes c.done, cancels each of c's children, and, if
// removeFromParent is true, removes c from its parent's children.
// cancel sets c.cause to cause if this is the first time c is canceled.
func (c *cancelCtx) cancel(removeFromParent bool, err, cause error) {
  //主动cancel,err类型为Canceled
  //父context cancel导致的cancel,err是父context的err
  //所以err为nil,就是发生了错误,则panic
  if err == nil {
    panic("context: internal error: missing cancel error")
  }
  if cause == nil {
    cause = err
  }
  c.mu.Lock()
  //c.err != nil为什么会是already canceled? 有可能是:
  //当我们执行c, cancel := context.WithCancel(context.Background())时,
  //返回值cancel是CancelFunc类型,内容为func() { c.cancel(true, Canceled, nil) }
  //调用cancel(),err是Canceled,cause为nil,主动cancel
  if c.err != nil {
    c.mu.Unlock()
    return // already canceled
  }
  c.err = err
  c.cause = cause
  d, _ := c.done.Load().(chan struct{})
  if d == nil {
    c.done.Store(closedchan)
  } else {
    close(d)
  }
  for child := range c.children {
    // NOTE: acquiring the child's lock while holding parent's lock.
    child.cancel(false, err, cause)
  }
  c.children = nil
  c.mu.Unlock()
​
  //是否需要把当前cancelCtx从父context的children map中移除
  if removeFromParent {
    //在newCancelCtx(parent Context) *cancelCtx时,会将参数parent赋给c.Context
    removeChild(c.Context, c)
  }
}
​
// removeChild removes a context from its parent.
func removeChild(parent Context, child canceler) {
  //判断parent是否是cancelCtx,只有cancelCtx才有children map
  p, ok := parentCancelCtx(parent)
  if !ok {
    return
  }
  p.mu.Lock()
  if p.children != nil {
    delete(p.children, child)
  }
  p.mu.Unlock()
}

5.2 构造CancelCtx实例经历了哪些步骤?

WithCancel() --> withCancel() --> newCancelCtx() --> propagateCancel() --> parentCancelCtx()

//当某个具备cancel的context需要被cancel时,可以调用此方法
//此方法会在cancelCtx、timerCtx构造时返回
type CancelFunc func()//1.构造CancelCtx时,首先会调用WithCancel()方法,同时返回cancelCtx与一个CancelFunc的方法
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
  c := withCancel(parent)
  return c, func() { c.cancel(true, Canceled, nil) }
}
​
func withCancel(parent Context) *cancelCtx {
  //检验父context非空
  if parent == nil {
    panic("cannot create context from nil parent")
  }
  //基于父context,构造出新的子context
  c := newCancelCtx(parent)
  //传递cancel事件,父context cancel时,子context也会cancel
  propagateCancel(parent, c)
  return c
}
​
// newCancelCtx returns an initialized cancelCtx.
func newCancelCtx(parent Context) *cancelCtx {
  return &cancelCtx{Context: parent}
}
​
// propagateCancel用于传递父子context之间的cancel事件
//父context被cancel时,子context也会被cancel
//cancelCtx是canceler的实现类
func propagateCancel(parent Context, child canceler) {
  done := parent.Done()
  //判断parent是否是可以被cancel的类型
  if done == nil {
    //emptyCtx是不会被cancel的类型
    return // parent is never canceled
  }
​
  //如果当前parent被cancel,则子context直接也被cancel
  select {
  case <-done:
    // parent is already canceled
    child.cancel(false, parent.Err(), Cause(parent))
    return
  default:
  }
​
  //判断:如果parent是cancelCtx类型,将子context加入parent的cchildren map[canceler]struct{} 当中
  if p, ok := parentCancelCtx(parent); ok {
    //向parent的cchildren map[canceler]struct{} 当中写数据加写锁
    p.mu.Lock()
    if p.err != nil {
      // parent has already been canceled
      child.cancel(false, p.err, p.cause)
    } else {
      if p.children == nil {
        p.children = make(map[canceler]struct{})
      }
      p.children[child] = struct{}{}
    }
    p.mu.Unlock()
  } else {
    //parent不是cancelCtx类型,但是又存在cancel的能力(在此方法开头第二行就判断了parent是否是可以被cancel的类型)
    //启动一个协程,通过多路复用的方式监控parent的状态,如果parent终止,则同时终止子context,
    //但如果子context终止,是与父context也就是parent无关的
    goroutines.Add(1)
    go func() {
      select {
      case <-parent.Done():
        child.cancel(false, parent.Err(), Cause(parent))
      case <-child.Done():
      }
    }()
  }
}
​
//判断父context是否是*cancelCtx类型
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
  done := parent.Done()
  //parent已经被关闭或者是不会被cance的类型
  if done == closedchan || done == nil {
    return nil, false
  }
​
  //*cancelCtx的Value()方法会比较参数key是否和&cancelCtxKey相同并返回,
  //如果相同就会返回*cancelCtx本身,可以再次被断言成*cancelCtx
  // func (c *cancelCtx) Value(key any) any {
  //  if key == &cancelCtxKey {
  //    return c
  //  }
  //  return value(c.Context, key)
  // }
  //此时传递的参数就是&cancelCtxKey,如果parent指向的是*cancelCtx类型,ok==true
  p, ok := parent.Value(&cancelCtxKey).(*cancelCtx)
  if !ok {
    return nil, false
  }
  //进一步判断*cancelCtx是否被用户自定义实现过
  pdone, _ := p.done.Load().(chan struct{})
  if pdone != done {
    return nil, false
  }
  //返回*cancelCtx,true
  return p, true
}

思考:当程序执行context.WithCancel(context.Background)时发生了什么?

6.timerCtx

6.1 timerCtx类定义

type timerCtx struct {
  *cancelCtx         //在cancelCtx的基础上,增加超时功能
  timer *time.Timer // Under cancelCtx.mu.
​
  deadline time.Time
}
​
//context接口的DeadLine()仅在timerCtx中有效,用于返回过期时间
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) {
  //调用继承的cancelCtx的cancel
  c.cancelCtx.cancel(false, err, cause)
  if removeFromParent {
    // Remove this timerCtx from its parent cancelCtx's children.
    removeChild(c.cancelCtx.Context, c)
  }
  c.mu.Lock()
  if c.timer != nil {
    c.timer.Stop()
    c.timer = nil
  }
  c.mu.Unlock()
}
​
// removeChild removes a context from its parent.
func removeChild(parent Context, child canceler) {
  //判断parent是否是cancelCtx,只有cancelCtx才有children map
  p, ok := parentCancelCtx(parent)
  if !ok {
    return
  }
  p.mu.Lock()
  if p.children != nil {
    delete(p.children, child)
  }
  p.mu.Unlock()
}

6.2 构造timerCtx经厉了哪些步骤?

//我们常用的带有超时功能的context,其实也是调用的WithDeadline()方法
//不过WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
  return WithDeadline(parent, time.Now().Add(timeout))
}
​
//d:context在d到来的时刻过期
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
  if parent == nil {
    panic("cannot create context from nil parent")
  }
  //判断parent是否是timerCtx并且过期时间是否早于自己
  if cur, ok := parent.Deadline(); ok && cur.Before(d) {
    // The current deadline is already sooner than the new one.
    return WithCancel(parent)
  }
  //构造新的*timerCtx
  c := &timerCtx{
    cancelCtx: newCancelCtx(parent),
    deadline:  d,
  }
  //传递父子context之间的cancel事件
  //但是如果parent是emptyCtx,是没有cancel能力的,此时子context是不能通过父context终止的
  propagateCancel(parent, c)
  //过期时间与当前时间的差值
  dur := time.Until(d)
  if dur <= 0 {
    //过期时间已到,cancel timerCtx,返回DeadlineExceeded类型错误
    c.cancel(true, DeadlineExceeded, nil) // deadline has already passed
    return c, func() { c.cancel(false, Canceled, nil) }
  }
  //加锁,启动timer,当timer时间到了,cancel timerCtx,返回DeadlineExceeded类型错误
  //即便parent是empytCtx,也可在timer到期时被自动cancel,或者调用cancelFunc cancel
  c.mu.Lock()
  defer c.mu.Unlock()
  if c.err == nil {
    c.timer = time.AfterFunc(dur, func() {
      c.cancel(true, DeadlineExceeded, nil)
    })
  }
  return c, func() { c.cancel(true, Canceled, nil) }
}

7.valueCtx

7.1valueCtx类定义

type valueCtx struct {
  Context
  key, val any
}
​
// stringify tries a bit to stringify v, without using fmt, since we don't
// want context depending on the unicode tables. This is only used by
// *valueCtx.String().
func stringify(v any) string {
  switch s := v.(type) {
  case stringer:
    return s.String()
  case string:
    return s
  }
  return "<not Stringer>"
}
​
func (c *valueCtx) String() string {
  return contextName(c.Context) + ".WithValue(type " +
    reflectlite.TypeOf(c.key).String() +
    ", val " + stringify(c.val) + ")"
}
​
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 {
    //ctx := c.(type)类型判断,判断接口是否是指定类型,
    //从而做出不同的行为,type-switch是固定写法,多态的一种表现形式
    switch ctx := c.(type) {
    case *valueCtx:
      //如果与当前valeCtx.key,返回value,否则继续向上寻找
      if key == ctx.key {
        return ctx.val
      }
      c = ctx.Context
    case *cancelCtx:
      //特定协议:如果key是&cancelCtxKey,返回*cancelCtx本身,否则继续向上寻找
      if key == &cancelCtxKey {
        return c
      }
      c = ctx.Context
    case *timerCtx:
      //timerCtx继承了*cancelCtx,所以判断key是不是&cancelCtxKey,
      //是就返回timerCtx中继承的*cancelCtx即可,不是就继续向上寻找
      if key == &cancelCtxKey {
        return ctx.cancelCtx
      }
      c = ctx.Context
    case *emptyCtx:
      //找不到当前的key
      return nil
    default:
      //其他自定义实现context接口的类型
      return c.Value(key)
    }
  }
}

7.2 构造valueCtx时发生了什么?

实例化一个valueCtx还是比较简单的。

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}
}
​