context的作用
Go语言中的context
包提供了一种在进程中跨API和跨进程边界传递取消信号、超时和其他请求范围的值的方式。context
的主要作用包括:
取消信号(Cancellation):
- 当一个操作需要取消时,可以通过
context
传递一个取消信号。例如,一个长运行的任务可以通过检查context
是否被取消来提前终止。
超时(Timeouts):
context
可以用来设置操作的超时时间。如果操作在指定时间内没有完成,context
会发送一个取消信号。
截止时间(Deadlines):
- 类似于超时,截止时间允许你设置一个绝对时间点,超过这个时间点后,
context
会发送取消信号。
请求范围的值(Request-scoped values):
context
可以在处理HTTP请求、数据库事务等过程中,存储请求相关的值,这些值可以跨API边界传递而不需要在每个函数调用中显式传递。
并发控制(Concurrency control):
- 在并发编程中,
context
可以用来同步多个goroutine,确保它们在某个操作被取消时能够及时响应。
父子关系(Parent-child relationships):
context
可以创建父子关系,子context
会继承父context
的取消信号。这在处理树状结构的并发任务时非常有用。
避免共享可变状态:
- 通过使用
context
,可以避免在多个goroutine之间共享可变状态,从而减少竞态条件和锁的使用。
API设计:
context
为API设计提供了一种标准的方式来处理取消和超时,使得API的使用更加一致和可预测。
使用context
时,通常会创建一个context.Context
类型的变量,然后通过context.WithCancel
、context.WithDeadline
、context.WithTimeout
或context.WithValue
等函数来创建一个新的context
,这个新的context
会包含额外的信息或取消功能。在实际的函数调用中,context.Context
作为第一个参数传递,以确保可以在需要时访问和使用这些值或信号。
context的实现原理:
context.Context
type Context interface {Deadline() (deadline time.Time, ok bool)Done() <-chan struct{}Err() errorValue(key any) any
}
Deadline():
返回过期时间,第一个返回值为过期时间,第二个返回值代表过期时间是否存在。
Done() <-chan struct{}
监测context是否终止,返回值是一个只读的通道,只有当context终止时才能从通道读取到数据。
Err() error
返回错误,通常情况下是context过期或者被手动取消。
Value(key any) any
返回 context 中的对应 key 的值.
context.error
var Canceled = errors.New("context canceled")var DeadlineExceeded error = deadlineExceededError{}type deadlineExceededError struct{}func (deadlineExceededError) Error() string { return "context deadline exceeded" }
func (deadlineExceededError) Timeout() bool { return true }
func (deadlineExceededError) Temporary() bool { return true }
Canceled:
context被cancel时报错;
DeadlineExceeded:
context超时时报错
context.emptyCtx
type emptyCtx struct{}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 any) any {return nil
}
Deadline:
返回一个默认时间和false表示不存在过期时间
Done:
返回一个nil值,用户读取时陷入阻塞状态
Err:
返回一个nil值,表示没有错误
Value:
返回一个nil值,表示没有值
context.Background&&context.TODO
type backgroundCtx struct{ emptyCtx }func (backgroundCtx) String() string {return "context.Background"
}type todoCtx struct{ emptyCtx }func (todoCtx) String() string {return "context.TODO"
}func Background() Context {return backgroundCtx{}
}func TODO() Context {return todoCtx{}
}
context.Background() 和 context.TODO() 方法返回的均是 emptyCtx 类型的一个实例.但是context.TODO()说明可能需要实现更加详细的context
cancelCtx
type canceler interface {cancel(removeFromParent bool, err, cause error)Done() <-chan struct{}
}type cancelCtx struct {Contextmu sync.Mutex done atomic.Value children map[canceler]struct{} err error cause error
}
Context:
指向有且仅有一个的父节点
Deadline:
未实现该方法,不能直接调用
Done:
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{})
}
基于 atomic 包,读取 cancelCtx 中的 chan,倘若已存在,则直接返回,否则加互斥锁,然后再次读取,若存在直接返回,否则初始化chan储存到atomic.Value包中,并返回(懒加载:在应用程序初始化时,不会立即加载所有数据,而是等到需要使用数据时才进行加载)
Err:
func (c *cancelCtx) Err() error {c.mu.Lock()err := c.errc.mu.Unlock()return err
}
加锁,读取错误,解锁
Value:
func (c *cancelCtx) Value(key any) any {if key == &cancelCtxKey {return c}return value(c.Context, key)
}
如果key等于特定值 &cancelCtxKey,则返回cancelCtx自身的指针。否则遵循valueCtx的思路取值返回
mu:
互斥锁,用于并发保护
done:
用于反应生命周期
children:
因为map的value是struct所以,返回的是set而不是map
canceler:
子节点的信息
err:
存放错误信息
cause:
用于提供更详细的错误信息,指示context
被取消的具体原因
context.WithCancel()
WithCancel
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {c := withCancel(parent)return c, func() { c.cancel(true, Canceled, nil) }
}func withCancel(parent Context) *cancelCtx {if parent == nil {panic("cannot create context from nil parent")}c := &cancelCtx{}c.propagateCancel(parent, c)return c
}
首先校验父context不为空,在 propagateCancel 方法内启动一个守护协程,以保证父 context 终止时,该 cancelCtx 也会被终止;返回cancelCtx以及用于终止cancelCtx的闭包函数。
propagateCancel
func (c *cancelCtx) propagateCancel(parent Context, child canceler) {c.Context = parentdone := parent.Done()if done == nil {return // parent is never canceled}select {case <-done:// parent is already canceledchild.cancel(false, parent.Err(), Cause(parent))returndefault:}if p, ok := parentCancelCtx(parent); ok {// parent is a *cancelCtx, or derives from one.p.mu.Lock()if p.err != nil {// parent has already been canceledchild.cancel(false, p.err, p.cause)} else {if p.children == nil {p.children = make(map[canceler]struct{})}p.children[child] = struct{}{}}p.mu.Unlock()return}if a, ok := parent.(afterFuncer); ok {// parent implements an AfterFunc method.c.mu.Lock()stop := a.AfterFunc(func() {child.cancel(false, parent.Err(), Cause(parent))})c.Context = stopCtx{Context: parent,stop: stop,}c.mu.Unlock()return}goroutines.Add(1)go func() {select {case <-parent.Done():child.cancel(false, parent.Err(), Cause(parent))case <-child.Done():}}()
}
首先将父context注入子context,如果父context是不会被cancel的类型,则直接返回。如果父context已经被cancel,则直接终止子context,并以父context的err作为子context的err。如果父context是cancelCtx的类型则加锁,并将子context添加到parent的children map当中。如果父context不是cancelCtx的类型,但是具备cancel的能力,则开启一个协程通过多路复用的方式监控 父contxet状态,倘若其终止,则同时终止子context,并透传父contxet的err。
parentCancelCtx
func parentCancelCtx(parent Context) (*cancelCtx, bool) {done := parent.Done()if done == closedchan || done == nil {return nil, false}p, ok := parent.Value(&cancelCtxKey).(*cancelCtx)if !ok {return nil, false}pdone, _ := p.done.Load().(chan struct{})if pdone != done {return nil, false}return p, true
}
如果传入的父context是不能被cancel的节点则返回false,如果用特定的cancelCtxKey取值得到的不是本身,说明不是cancelCtx的类型,返回false(详见之前的Value)
cancel
func (c *cancelCtx) cancel(removeFromParent bool, err, cause error) {if err == nil {panic("context: internal error: missing cancel error")}if cause == nil {cause = err}c.mu.Lock()if c.err != nil {c.mu.Unlock()return // already canceled}c.err = errc.cause = caused, _ := 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, cause)}c.children = nilc.mu.Unlock()if removeFromParent {removeChild(c.Context, c)}
}
方法有两个入参,第一个表示表示当前 context 是否需要从父 context 的 children set 中删除,第二个表示要返回的具体的错误。
首先校验是否存在err,若为空则 panic,然后检验cause是否为空,若为空则将传入的err赋值给cause,然后加锁判断当前cancelCtx的err是否为空,若不为空说明已经被cancel,直接返回。否则将传入的err赋值给当前cancelCtx的err,将cause赋值给当前cancelCtx的cause,处理当前cancelCtx的channel,若 channel 此前未初始化则直接注入一个 closedChan,否则关闭channel。遍历当前 cancelCtx 的 children set,依次将 children context 都进行cancel,解锁,根据传入的 removeFromParent flag 判断是否需要手动把 cancelCtx 从 parent 的 children set 中移除。
removeChild
func removeChild(parent Context, child canceler) {if s, ok := parent.(stopCtx); ok {s.stop()return}p, ok := parentCancelCtx(parent)if !ok {return}p.mu.Lock()if p.children != nil {delete(p.children, child)}p.mu.Unlock()
}
timerCtx
type timerCtx struct {cancelCtxtimer *time.Timer // Under cancelCtx.mu.deadline time.Time
}
在cancelCtx的基础上又进行了一层封装。新增了一个 time.Timer 用于定时终止 context;另外新增了一个 deadline 字段用于字段 timerCtx 的过期时间.
timerCtx.Deadline
func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {return c.deadline, true
}
用于展示过期时间
timerCtx.cancel
func (c *timerCtx) cancel(removeFromParent bool, err, cause error) {c.cancelCtx.cancel(false, err, cause)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()
}
复用继承的 cancelCtx 的 cancel 能力,进行 cancel 处理,加锁,停止定时终止context,解锁。
context.WithTimeout & context.WithDeadline
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) {return WithDeadlineCause(parent, d, nil)
}func WithDeadlineCause(parent Context, d time.Time, cause error) (Context, CancelFunc) {if parent == nil {panic("cannot create context from nil parent")}if cur, ok := parent.Deadline(); ok && cur.Before(d) {// The current deadline is already sooner than the new one.return WithCancel(parent)}c := &timerCtx{deadline: d,}c.cancelCtx.propagateCancel(parent, c)dur := time.Until(d)if dur <= 0 {c.cancel(true, DeadlineExceeded, cause) // deadline has already passedreturn c, func() { c.cancel(false, Canceled, nil) }}c.mu.Lock()defer c.mu.Unlock()if c.err == nil {c.timer = time.AfterFunc(dur, func() {c.cancel(true, DeadlineExceeded, cause)})}return c, func() { c.cancel(true, Canceled, nil) }
}
WithTimeout传入过期所用的时间,WithDeadline传入过期时的时间。
首先检验父context是否为空,检验父context的过期时间是否早于自己,如果早于自己,则直接构造一个cancelCtx即可,否则构造一个timerCtx, 传入过期时间,启动守护方法,同步 parent 的 cancel 事件到子 context,判断过期时间是否已到,如果已到,直接cancel,返DeadlineExceeded错误。加锁,如果当前context的err为空,启动time.Timer,设定一个延时时间,即达到过期时间后会终止该timerCtx,并返回DeadlineExceeded错误,解锁,返回timerCtx,以及封装了cancel逻辑的闭包cancel函数
valueCtx
type valueCtx struct {Contextkey, val any
}
一个 valueCtx 中仅有一组 kv 对
valueCtx.Value
func (c *valueCtx) Value(key any) any {if c.key == key {return c.val}return value(c.Context, key)
}func value(c Context, key any) any {for {switch ctx := c.(type) {case *valueCtx:if key == ctx.key {return ctx.val}c = ctx.Contextcase *cancelCtx:if key == &cancelCtxKey {return c}c = ctx.Contextcase withoutCancelCtx:if key == &cancelCtxKey {// This implements Cause(ctx) == nil// when ctx is created using WithoutCancel.return nil}c = ctx.ccase *timerCtx:if key == &cancelCtxKey {return &ctx.cancelCtx}c = ctx.Contextcase backgroundCtx, todoCtx:return nildefault:return c.Value(key)}}
}
假如当前 valueCtx 的 key 等于用户传入的 key,则直接返回其 value,否则从父context 中依次向上寻找,启动一个 for 循环,由下而上,由子及父,依次对 key 进行匹配,找到匹配的 key,则将该组 value 进行返回。
context.WithValue
func WithValue(parent Context, key, val any) Context {if parent == nil {panic("cannot create context from nil parent")}if key == nil {panic("nil key")}if !reflectlite.TypeOf(key).Comparable() {panic("key is not comparable")}return &valueCtx{parent, key, val}
}
如果父context为空panic,如果key为空panic,如果key的类型无法比较panic,包含父context和kv对,创建一个新的valueCtx并返回。