Context
一个接口,四种实现,六种创建方法
一个接口
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
Go 的 Context接口的数据结构包含 Deadline,Done,Err,Value四个基本方法Deadline 方法返回一个 time.Time,表示当前 Context 应该结束的时间,ok 则表示有结束时间,Done 方法当 Context 被取消或者超时时候返回的一个 close 的 channel,告诉给 context 相关的函数要停止当前工作然后返回了,Err 表示 context 被取消的原因,Value 方法表示 context 实现共享数据存储的地方,是协程安全的。context 在业务中是经常被使用的。
四种实现
- emptyCtx 实现了一个空的context,可以用作根节点
- cancelCtx 实现一个带cancel功能的context,可以主动取消
- timerCtx 实现一个通过定时器timer和截止时间deadline定时取消的context
- valueCtx 实现一个可以通过 key、val 两个字段来存数据的context
六种创建方法
- Background 返回一个emptyCtx作为根节点
- TODO 返回一个emptyCtx作为未知节点
- WithCancel 返回一个cancelCtx
- WithDeadline 返回一个timerCtx
- WithTimeout 返回一个timerCtx
- WithValue 返回一个valueCtx
使用场景
- 上下文控制
- 多个goroutine之间数据交互
- 超时控制
emptyCtx
type emptyCtx int
func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
return
}
func (*emptyCtx) Done() <-chan struct{} {
return nil
}
func (*emptyCtx) Err() error {
return nil
}
func (*emptyCtx) Value(key interface{}) interface{} {
return nil
}
emptyCtx实现了空的Context接口,其主要作用是为Background和TODO这两个方法都会返回预先初始化好的私有变量 background 和 todo,它们会在同一个 Go 程序中被复用
Background和Todo得到的context有什么区别?
Background通常被用于主函数、初始化以及测试中,作为一个顶层的context,也就是说一般我们创建的context都是基于Background;而TODO是在不确定使用什么context的时候才会使用。
cancelCtx
父子绑定重点方法
func propagateCancel(parent Context, child canceler) {
// 1.如果 parent ctx 是不可取消的 ctx,则直接返回 不进行关联
done := parent.Done()
if done == nil {
return // parent is never canceled
}
// 2.接着判断一下 父ctx 是否已经被取消
select {
case <-done:
// 2.1 如果 父ctx 已经被取消了,那就没必要关联了
// 然后这里也要顺便把子ctx给取消了,因为父ctx取消了 子ctx就应该被取消
// 这里是因为还没有关联上,所以需要手动触发取消
// parent is already canceled
child.cancel(false, parent.Err())
return
default:
}
// 3. 从父 ctx 中提取出 cancelCtx 并将子ctx加入到父ctx 的 children 里面
if p, ok := parentCancelCtx(parent); ok {
p.mu.Lock()
// double check 一下,确认父 ctx 是否被取消
if p.err != nil {
// 取消了就直接把当前这个子ctx给取消了
// parent has already been canceled
child.cancel(false, p.err)
} else {
// 否则就添加到 children 里面
if p.children == nil {
p.children = make(map[canceler]struct{})
}
p.children[child] = struct{}{}
}
p.mu.Unlock()
} else {
// 如果没有找到可取消的父 context。新启动一个协程监控父节点或子节点取消信号
atomic.AddInt32(&goroutines, +1)
go func() {
select {
case <-parent.Done():
child.cancel(false, parent.Err())
case <-child.Done():
}
}()
}
}
valueCtx
父子查询重点方法
func (c *valueCtx) Value(key interface{}) interface{} {
if c.key == key {
return c.val
}
return c.Context.Value(key)
}
递归相当于链表从尾开始查询
timerCtx
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) cancel(removeFromParent bool, err error) {
c.cancelCtx.cancel(false, err)
if removeFromParent {
removeChild(c.cancelCtx.Context, c)
}
c.mu.Lock()
if c.timer != nil {
c.timer.Stop()
c.timer = nil
}
c.mu.Unlock()
}