不同的api或者进程之间传递(
携带键值对传递),不要传递 nil Context
单向传递的
,只有父节点取消的时候,才会把取消的信号传递给父节点的衍生子节点ctx
本文都是围绕一下进行讨论的,无非就是实现这四个方法的逻辑不同。方法的具体实现,将会在下面具体讲解,现在先让大家对Context有个宏观的认知
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key any) any
}
Deadline()
: 返回一个完成工作的截止时间,这个截止时间代表上下文被取消的时间,这个时间之后所有的衍生协程都将会取消。如果没有截止时间,将会返回falseDone()
:返回一个Context中的channel,这个channel会在当前任务完成工作的时候关闭,如果关闭的时候无法取消上下文,则Done可能返回nil。多次调用Done方法会返回同一个channel。Err()
:返回Context结束的原因,他只会在Done
方法对应的Channel关闭的时候关闭返回非空值,如果context被取消,会返回context.Canceled
如果Context超时,会返回Context.DeadlineExceeded
错误Value()
:从Context从获取对应的键值对,如果未设置键值对则返回nil
。相同的key取value会返回相同的值。因为这四个方法都是幂等
的emptyCtx ()
: 实现了一个空的context用作根节点,TODO(),Background(),都是基于他实现的cancelCtx ()
: 实现一个能够自己取消的ContexttimerCtx ()
: 实现一个通过定时器timer和截止时间deadline定时取消的contextvalueCtx ()
: 实现一个可以通过 key、val 两个字段来存数据的contextBackground ()
: Background 返回一个emptyCtx作为根节点
TODO ()
: TODO 返回一个emptyCtx作为未知节点,如果你不知道使用哪个Context可以使用此方法
WithCancel()
: WithCancel 入参是一个父Context,return一个子Context和cancelCtx(主动取消Context),如果想取消Context可以调用cancelCtx()
WithDeadline ()
: WithDeadline 入参是一个父Context和一个过期时间, 返回一个子Context和timerCtx,如果时间超时,那么他会自动的调用timerCtx()方法
WithTimeout ()
: WithTimeout 入参是一个父Context和一段时间, 返回一个子Context和timerCtx,如果传入的是3秒,那么3秒后,他会自动的调用timerCtx()方法
WithCancelCause()
: WithCancelCause,和WithCancel不同点就是他可以自定义error而WithCancel不能
WithDeadlineCause()
: 同上
WithTimeoutCause()
: 同上
AfterFunc()
: AfterFunc入参是一个Context和一个func(),在该Context取消的时候,会执行该func。可以解决以往的一些合并取消上下文和串联处理的复杂场景。AfterFunc 安排在 ctx 完成后在自己的 goroutine 中调用 f(取消或超时)。如果 ctx 已经完成,AfterFunc 会立即在自己的 goroutine 中调用 f。
WithoutCancel()
: WithoutCance返回一个父级Context的副本,即使父Context关闭,生成的父Contest的副本也不会关闭。
从下面的err可以看出,有两个错误方法,Canceled
:Context取消时返回的err,DeadlineExceeded
:Context截止时间超时的err
// Canceled is the error returned by [Context.Err] when the context is canceled.
var Canceled = errors.New("context canceled")
// DeadlineExceeded is the error returned by [Context.Err] when the context's
// deadline passes.
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 }
字如其名就是一个空的Context,无法被取消,没有值,也没有超时时间。
// An emptyCtx is never canceled, has no values, and has no deadline.
// It is the common base of backgroundCtx and todoCtx.
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的flag。表示这个Context没有超时时间
Done()
: 方法返回一个nil,如果用户读取或者写入
这个Channel,会造成阻塞 Err(
: Err的错误返回一个nil,证明这个方法不会有err
Value
: Value值返回nil,证明这个方法不会有value
从源码可以看出这两个方法其实都是返回的emptyCtx 的一个实例,只是他们的表达方式不一样,他们两个继承
了emptyCtx 所有的特点,,无法被取消,没有值,也没有超时时间。
Background():上下文的默认值,相当于root
,所有的Context都应该从这里衍生出来,一般会在main中作为最顶层的Context
TODO():Todo就是表示这个地方还没有完成,代表目前还不知道用哪个Context传递,或者根本没有Context,但是业务又需要传递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{}
}
cancelCtx 是一个能够被取消的Context,其中他实现了Context接口,他是一个匿名字段,可以看成Context.
type canceler interface {
cancel(removeFromParent bool, err, cause error)
Done() <-chan struct{}
}
type cancelCtx struct {
Context
mu sync.Mutex // protects following fields
done atomic.Value // of chan struct{}, created lazily, closed by first cancel call
children map[canceler]struct{} // set to nil by the first cancel call
err error // set to non-nil by the first cancel call
cause error // set to non-nil by the first cancel call
}
Context
:嵌入了一个Context作为父Context,表示cancelCtx 肯定是某一个Context的子节点
mu
:互斥锁,用来保证并发安全
done
:实际的类型是 chan struct{}
,用来反应cancelCtx 生命周期的通道,如果是cancelCtx 关闭,那么就会向done发送信号,用来表示传递关闭信号。其中内部是使用atomic.Value
实现的,会读取上次最近存储的chan struct{}
children
:指向 cancelCtx 的所有子 context,在第一次调用的时候为nil
err
:记录了当前 cancelCtx 的错误.
cause
:自定义err,返回自定义的cancelCtx 错误
cancelCtx 未实现该方法,仅是 嵌入了一个带有 Deadline 方法的 Context interface,因此倘若直接调用会报错.
Done返回一个只读的通道,当Context取消的时候,该通道就会关闭,你可以监听这个通道检测Context是否被关闭,将done初始化。
是否存储了chan struct{}
,如果存储的有就返回原来的实例化的chan struct{}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{})
}
这个就是读取cancelCtx的err,然后进行返回
func (c *cancelCtx) Err() error {
c.mu.Lock()
err := c.err
c.mu.Unlock()
return err
}
倘若 key 特定值 &cancelCtxKey,则返回 cancelCtx 自身的指针,否则就会取值return
func (c *cancelCtx) Value(key any) any {
if key == &cancelCtxKey {
return c
}
return value(c.Context, key)
}
了解了cancelCtx 结构之后进入真正的主题
WithCancel():返回的是一个继承了父Context
的新Context(cancelCtx),和一个CancelFunc方法,如果调用CancelFunc,这个context 将会取消。
WithCancelCause():返回的内容一样,就是CancelFunc方法多了一个自定义的err。下面是使用示例,myError是自定义的err。
ctx, cancel := context.WithCancelCause(parent)
cancel(myError)
//返回myError
context.Cause(ctx)
type CancelFunc func()
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
c := withCancel(parent)
return c, func() { c.cancel(true, Canceled, nil) }
}
type CancelCauseFunc func(cause error)
func WithCancelCause(parent Context) (ctx Context, cancel CancelCauseFunc) {
c := withCancel(parent)
return c, func(cause error) { c.cancel(true, Canceled, cause) }
}
func withCancel(parent Context) *cancelCtx {
if parent == nil {
panic("cannot create context from nil parent")
}
c := &cancelCtx{}
c.propagateCancel(parent, c)
return c
}
如果父context取消了,那么他的所有衍生context都应该被取消
func (c *cancelCtx) propagateCancel(parent Context, child canceler) {
c.Context = parent
done := parent.Done()
if done == nil {
return // parent is never canceled
}
select {
case <-done:
// parent is already canceled
child.cancel(false, parent.Err(), Cause(parent))
return
default:
}
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 canceled
child.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():
}
}()
}
c.Context = parent,把该context指向父context,这样就知道父context有哪些子context了
判断父context是否为不可取消类型,如果是,就跳过,如果不是就监听父context是否已经取消了,如果是取消了就把该子context取消。并把父err,传递给子err
如果parent 是 cancelCtx 的类型,则加锁,并将子 context 添加到 parent 的 children map 当中,假如以后父context取消,可以直接用children map 取消所有的子context
如果parent 实现了afterFuncer接口,把cancel函数封装成一个stop函数,进行延迟取消。赋值给子context的结构体中。后续会调用这个afterFuncer
如果 parent 没有实现afterFuncer接口,则启动一个协程,通过多路复用的方式监控 parent 状态,如果终止,则同时终止子 context,并把parent 的 err传递给子context,如果孩子终止则直接跳过,因为不影响parent 。这里启动协程是主要监听parent,如果parent取消则立即取消子context。
在propagateCancel守护协程中,知道了怎么终止协程cancel(false, parent.Err(), Cause(parent))。但是你知道怎么实现的吗?来看源代码
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 = err
c.cause = cause
d, _ := 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 = nil
c.mu.Unlock()
if removeFromParent {
removeChild(c.Context, c)
}
}
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()
}
type timerCtx struct {
cancelCtx
timer *time.Timer // Under cancelCtx.mu.
deadline time.Time
}
因为这四个方法实现的逻辑都基本是一样的,下面将会讲解怎么实现具体逻辑
timerCtx 在 cancelCtx 基础上又做了一层封装,他继承了cancelCtx结构体,实现了cancelCtx的所有能力,然后外加了一个定时取消的Context,和到截止时间取消的Context
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
}
在之前讲解的 WithCancel() 和 WithCancelCause()中,都没有实现Deadline()这个方法,这个方法用于展示过期时间
因为他继承了cancelCtx,所以可以之间调用cancelCtx.cancel,进行取消操作,然后判断是否需要把孩子context从map中删除,然后停止timer
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()
}
这个是把context进行name的拼接,调用这个方法会把context的名字return
func (c *timerCtx) String() string {
return contextName(c.cancelCtx.Context) + ".WithDeadline(" +
c.deadline.String() + " [" +
time.Until(c.deadline).String() + "])"
}
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
return WithDeadlineCause(parent, d, nil)
}
// WithDeadlineCause behaves like [WithDeadline] but also sets the cause of the
// returned Context when the deadline is exceeded. The returned [CancelFunc] does
// not set the cause.
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 passed
return 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) }
}
设定一个延时时间
,到过期时间的时候会终止该 timerCtx,并返回 的错误。最后返回一个timerCtx和cancel函数