go的context.WithTimeout学习

context.WithTimeout

ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(5))

调用的是 WithDeadline
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
   return WithDeadline(parent, time.Now().Add(timeout))
}

WithDeadline 返回的是
c := &timerCtx{
   cancelCtx: newCancelCtx(parent),
   deadline:  d,
   // time.AfterFunc 是创建并启动一个定时timer,到期后调用func(){}方法
   timer: time.AfterFunc(dur, func() {
      // DeadlineExceeded 是一个error
      // 超时之后调用timerCtx的cancel方法
      c.cancel(true, DeadlineExceeded)
   })
}
// DeadlineExceeded 实现了Error接口
var DeadlineExceeded error = deadlineExceededError{}
type deadlineExceededError struct{}
func (deadlineExceededError) Error() string   { return "context deadline exceeded" }
func (c *timerCtx) cancel(removeFromParent bool, err error) {
   // 调用的是 cancelCtx 的 cancel 方法
   c.cancelCtx.cancel(false, err)
   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()
}

// cancel closes c.done, cancels each of c's children, and, if
// removeFromParent is true, removes c from its parent's children.
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
   // err必须存在,不然无法做cancel的重复调用cancel的动作
   if err == nil {
      panic("context: internal error: missing cancel error")
   }
   c.mu.Lock()
   // 已经取消
   if c.err != nil {
      c.mu.Unlock()
      return
   }
   
   // 第一次进入后,将err保存到cancelCtx中
   c.err = err
   // 保证 c.done中存储的channel是已经关闭的
   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)
   }
   c.children = nil
   c.mu.Unlock()

   if removeFromParent {
      removeChild(c.Context, c)
   }
}


// closechan的定位,出生就被关闭了
// closedchan is a reusable closed channel.
var closedchan = make(chan struct{})

func init() {
   close(closedchan)
}

context.Done

ctx.Done()

上文已经说了,返回的ctx是timerCtx,timerCtx用的是cancelCtx的Done
所以我们看timerCtx的Done方法

// 使用了Double-Compare机制,CAS
func (c *cancelCtx) Done() <-chan struct{} {
   // atomic.Value 中存储一个 chan struct{},并隐式转换为只读chan,即<-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{})
}

cancel的时候,往里面存储了closedchan,这里拿出来
利用 for range 来跳出,这样for range面对一个已经close的chan,就不会再次死循环了

如果是主动取消,通过上文return c, func() { c.cancel(true, Canceled) }可知
这里的主动取消效果同被动取消的一致

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
}

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.
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
   if err == nil {
      panic("context: internal error: missing cancel error")
   }
   c.mu.Lock()
   if c.err != nil {
      c.mu.Unlock()
      return // already canceled
   }
   c.err = err
   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)
   }
   c.children = nil
   c.mu.Unlock()

   if removeFromParent {
      removeChild(c.Context, c)
   }
}

timerCtx

// 看注释,timerCtx嵌入cancelCtx来实现context的Done和Error
// 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
}

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 error) {
   c.cancelCtx.cancel(false, err)
   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()
}

关系

  • context.Context 是个接口类型,它实现了 Deadline(),Done(),Err() 和 Value(key interface{}) 方法。各方法的功能如下:
    • Deadline: 返回上下文 context.Context 的截止时间,截止时间到将取消该上下文。
    • Done: 返回只读空结构体通道。源码中没有向该通道写结构体,调用该方法会使通道阻塞在接收数据,直到关闭该通道(关闭通道会读到结构体的零值)。
    • Err: 返回上下文 context.Context 结束的错误类型。有两种错误类型:
      • 如果 context.Context 被取消,则返回 canceled 错误;
      • 如果 context.Context 超时,则返回 DeadlineExceeded 错误。
    • Value: 返回 context.Context 存储的键 key 对应的值。
  • canceler 也是一个接口,该接口实现了 cancel(removeFromParent bool, err error) 和 Done() 方法。实现了该接口的上下文 context.Context 均能被取消(通过调用 cancel 方法取消)。
  • cancelCtx 和 timerCtx(timerCtx 内嵌了 cancelCtx 结构体) 均实现了 canceler 接口,因此这两类上下文是可取消的。
  • emptyCtx 是空的上下文。它被 Backgroud 函数调用作为父上下文或被 ToDo 函数调用,用于不明确传递什么上下文 context.Context 时使用。
  • valueCtx 存储键值对的上下文

你可能感兴趣的:(go,golang,学习)