前言
一文带你快速入门contextdefer cancelFunc()
func main() {
    parent := context.Background()
    for i := 0; i < 100; i++ {
        go doRequest(parent)
    }
    time.Sleep(time.Second * 10)
}

// doRequest 模拟网络请求
func doRequest(parent context.Context) {
    ctx, _ := context.WithTimeout(parent, time.Second*5)
    time.Sleep(time.Millisecond * 200)
    go func() {
        <-ctx.Done()
        fmt.Println("ctx done!")
    }()
}
maindoRequestdoRequest5sdoRequest200ms

<img src="https://images-1255831004.cos.ap-guangzhou.myqcloud.com/halo/2021-07-10-142940.png" alt="image-20210710222940035" style="zoom:50%;" />

doRequest
defer cancelFunc()

主动调用cancelFunc是一个好习惯!

了解一下Context接口
type Context interface {
  // [1] 返回上下文的截止时间
    Deadline() (deadline time.Time, ok bool)
  // 返回一个通道,当上下文结束时,会关闭该通道,此时 <-ctx.Done() 结束阻塞
  Done() <-chan struct{}
  // [2] 该方法会在上下文结束时返回一个not nil err,该err用于表示上下文结束的原因
  Err() error
  // 返回与key关联的上下文的value
  Value(key interface{}) interface{}
}
[1]处Deadlineok = false
func main() {
    ctx, cancelFunc := context.WithCancel(context.Background())
    defer cancelFunc()
    deadline, ok := ctx.Deadline()
    fmt.Printf("ok = %v, deadline = %v\n", ok, deadline)
    // 输出 ok = false, deadline = 0001-01-01 00:00:00 +0000 UTC
}
[2]处Errnot nil
func main() {
  // 设置上下文10s的超时时间
    ctx, cancelFunc := context.WithTimeout(context.Background(), time.Second * 10)
    go func() {
        // 1s后主动取消上下文
        <-time.After(time.Second)
        cancelFunc()
    }()
    <-ctx.Done()
    err := ctx.Err()
    fmt.Printf("err == nil ? %v\n", err == nil)
    // 输出 err == nil ? false
}
有几个结构体不能错过
Contextcontext4种上下文

<img src="https://images-1255831004.cos.ap-guangzhou.myqcloud.com/halo/2021-07-10-151151.png" alt="image-20210710231150954" style="zoom:50%;" />

timerCtx
Context

cancelCtx

type cancelCtx struct {
    Context // [1] 匿名接口

    mu       sync.Mutex            // 这个锁是用来保护下面这些字段的
    done     chan struct{}         // [2] 这个channel的初始化方式为懒加载
    children map[canceler]struct{} 
    err      error                 
}

// 新建可取消的上下文
func newCancelCtx(parent Context) cancelCtx {
    return cancelCtx{Context: parent}
}

func (c *cancelCtx) Value(key interface{}) interface{} {
    if key == &cancelCtxKey {
        return c
    }
  // 搜索父级上下文的value
    return c.Context.Value(key)
}

func (c *cancelCtx) Done() <-chan struct{} {
    c.mu.Lock()
    if c.done == nil {
    // 懒加载,第一次调用Done方法的时候,channel才初始化
        c.done = make(chan struct{})
    }
    d := c.done
    c.mu.Unlock()
    return d
}

func (c *cancelCtx) Err() error {
    c.mu.Lock()
    err := c.err
    c.mu.Unlock()
    return err
}
[1]处cancelCtx
cancelCtxparent
ValueDoneErr
cancelCtxDeadlineparentDeadline
[2]处doneDonedone

timerCtx

type timerCtx struct {
    cancelCtx // [1] 内嵌结构体
    timer *time.Timer // [2] 用于实现截止时间

    deadline time.Time
}

func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
    return c.deadline, true
}

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
  // 构建超时上下文底层也是通过构建截止时间上下文
    return WithDeadline(parent, time.Now().Add(timeout))
}

func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
    if cur, ok := parent.Deadline(); ok && cur.Before(d) {
        // 当子上下文的截止时间超过父级上下文时,直接构造可取消的上下文并返回
        return WithCancel(parent)
    }
    c := &timerCtx{
        cancelCtx: newCancelCtx(parent),
        deadline:  d,
    }
    propagateCancel(parent, c)
    dur := time.Until(d)
    if dur <= 0 {
        c.cancel(true, DeadlineExceeded) // deadline has already passed
        return c, func() { c.cancel(false, Canceled) }
    }
    c.mu.Lock()
    defer c.mu.Unlock()
    if c.err == nil {
        c.timer = time.AfterFunc(dur, func() {
      // 定时器,到达截止时间后,结束上下文
            c.cancel(true, DeadlineExceeded)
        })
    }
    return c, func() { c.cancel(true, Canceled) }
}
[1]处timerCtxcancelCtxtimerCtxparenttimerCtxDeadline
[2]处timertimer.AfterFunccancel

valueCtx

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

func (c *valueCtx) Value(key interface{}) interface{} {
    if c.key == key {
        return c.val
    }
    return c.Context.Value(key) // 寻找父级上下文中是否包含与该key关联的值
}

func WithValue(parent Context, key, val interface{}) Context {
    if key == nil {
        panic("nil key")
    }
  // [1] 存入key的类型是不可比较时直接panic
    if !reflectlite.TypeOf(key).Comparable() {
        panic("key is not comparable")
    }
    return &valueCtx{parent, key, val}
}
valueCtxparentValue
[1]处
  • slice
  • map
  • func
valueCtxkeypanic
思考以下几个问题

上下文中的通道为什么要懒加载?

我的猜测是节省内存

cancel
closedchanclose
// 这是一个可重复使用的通道
var closedchan = make(chan struct{})

func init() {
  // 包初始化时,关闭通道
    close(closedchan)
}

func (c *cancelCtx) cancel(removeFromParent bool, err error) {
  ....
  if c.done == nil {
    // 如果done为空,就代表当前还没调用过Done方法,则直接使用closechan替代
        c.done = closedchan
    } else {
        close(c.done)
    }
  ....
}
Context.Done
closedchandone

多次调用cancelFunc会怎么样?

并不会怎么样,多次主动取消上下文不会产生任何错误

cancelFunccancelreturn
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 {
    // err不为空,代表上下文已经被取消掉了,直接结束流程
        c.mu.Unlock()
        return 
    }
  ...
}

<img src="https://images-1255831004.cos.ap-guangzhou.myqcloud.com/halo/2021-07-10-164732.jpg" alt="img" />

当前上下文的截止时间能否超过父级上下文的截止时间?

不能,此时上下文的截止时间会跟父级上下文的截止时间保持一致

WithDeadline
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
    if cur, ok := parent.Deadline(); ok && cur.Before(d) {
        // 当子上下文的截止时间超过父级上下文时,直接构造可取消的上下文并返回
        return WithCancel(parent)
    }
  ....
}

当返回一个可取消的上下文时,表示子上下文的截止时间跟父级上下文是一致的

background和todo的区别是什么?

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

func Background() Context {
    return background
}

func TODO() Context {
    return todo
}
TODO