Golang系列-context

103 阅读2分钟

概述

golang的context包主要有以下几个用途

  • 上下文传递
  • 超时控制
  • 取消子任务
  • 在多个协程间传递同步信号

使用

context.Context是一个接口,定义如下

type Context interface {
    Deadline() (deadline time.Time, ok book)
    Done() <-chan struct{}
    Err() error
    Value(key any) any
}
  • Done 会返回一个channel,当该context被取消时,channel会被关闭。
  • Context 的方法是协程安全的,可以传递给任意的goroutine并同时访问。
  • Deadline 可以返回一个截止时间。
  • Value 可以让协程共享数据,并且是协程安全的。

context提供了以下函数来创建子节点

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 interface{}, val interface{}) Context

一次请求可能会创建多个goroutine,形成一个routine树。通过context的层层传递可以使整个请求的routine树退出。

超时退出

为什么应该使用Context?

  • 每一个RPC调用都应该有超时退出的能力,这是比较合理的API设计。
  • 任何函数可能被阻塞,或者需要运行很长时间,都应该有个Context来结束不需要再操作的行为。
  • context是Go中标准的解决方案。

实现

上下文传递

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

func WithValue(parent Context, key, val interface{}) Context {
    if key == nil {
        panic("nil key")
    }
    ......
    return &valueCtx{parent, key, val}
}

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

Value查找是回溯树的方式(从下到上,从子Context到父Context)。

超时控制

cancelCtx 实现了canceler接口。

type cancelCtx struct {
    Context      // 保存parent Context
    done chan struct{}
    mu       sync.Mutex
    children map[canceler]struct{}
    err      error
}

// A canceler is a context type that can be canceled directly. The
// implementations are *cancelCtx and *timerCtx.
type canceler interface {
    cancel(removeFromParent bool, err error)
    Done() <-chan struct{}
}

cancelCtx结构体中children保存了所有的子canceler, 当外部触发cancel时,所有子canceler 也会执行 cancel()来终止所有的cancelCtx

done用来标识是否已被cancel。当context已被cancel、或者父Context的channel关闭时,当前的 done 也会关闭。

type timerCtx struct {
    cancelCtx     //cancelCtx.Done()关闭的时机:1)用户调用cancel 2)deadline到了 3)父Context的done关闭了
    timer    *time.Timer
    deadline time.Time
}

func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
    ......
    c := &timerCtx{
        cancelCtx: newCancelCtx(parent),
        deadline:  deadline,
    }
    propagateCancel(parent, c)
    d := time.Until(deadline)
    if d <= 0 {
        c.cancel(true, DeadlineExceeded) // deadline has already passed
        return c, func() { c.cancel(true, Canceled) }
    }
    c.mu.Lock()
    defer c.mu.Unlock()
    if c.err == nil {
        c.timer = time.AfterFunc(d, func() {
            c.cancel(true, DeadlineExceeded)
        })
    }
    return c, func() { c.cancel(true, Canceled) }
}