概述
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) }
}