context原理与关键代码分析

context原理与关键代码分析

type Context interface

Context 是 Go 语言标准库中的一个接口,定义了与请求的生命周期相关的操作和值的传递。

type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key interface{}) interface{}
}

Context 接口定义了以下四个方法:

  1. Deadline() (deadline time.Time, ok bool):返回上下文的截止时间和一个布尔值,指示是否设置了截止时间。如果设置了截止时间,ok 的值为 true,否则为 false,可多次获取,返回相同的结果。截止时间用于限制操作的执行时间。
  2. Done() <-chan struct{}:返回一个只读通道(<-chan),当上下文被取消、超时或其他原因导致上下文结束时,该通道将被原子性写入var closedchan = make(chan struct{})。可以通过监听该通道来接收上下文结束的信号。
  3. Err() error:返回上下文结束的原因。如果上下文正常结束,返回 nil。如果上下文被取消,返回 context.Canceled 错误;如果上下文超时,返回 context.DeadlineExceeded 错误。
  4. Value(key interface{}) interface{}:返回与给定键关联的值,如果没有value关联到对应的key,会返回nil,重复调用会返回相同的值。上下文可以传递一些键值对的数据,供请求中的不同组件共享和访问。

通过这些方法,我们可以获取上下文的截止时间、接收上下文结束的信号、获取上下文的结束原因以及获取上下文中传递的值。

Context 接口的实现类型有多种,包括 context.Background()context.TODO()context.WithCancel()context.WithDeadline()context.WithTimeout() 等。通过这些函数创建的上下文对象可以用于控制并传递请求的上下文信息,实现请求的超时控制、取消操作、值传递等功能。

四种context struct
  1. emptyCtx 结构体:
type emptyCtx int

func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
    return time.Time{}, false
}

func (*emptyCtx) Done() <-chan struct{} {
    return closedchan
}

func (*emptyCtx) Err() error {
    return nil
}

func (*emptyCtx) Value(key interface{}) interface{} {
    return nil
}

  • emptyCtx 是一个空的结构体,没有任何成员变量。
  • Deadline() 方法返回一个零值的时间和 false,表示没有设置截止时间。
  • Done() 方法返回一个已关闭的通道,用于表示上下文的结束。
  • Err() 方法返回 nil,表示上下文没有发生任何错误。
  • Value() 方法返回 nil,表示上下文中没有传递任何值。
  • 该结构体是backgroundtodo的原型,直接new(emptyCtx)
  1. cancelCtx 结构体:
type cancelCtx struct {
    Context

    mu       sync.Mutex
    done     chan struct{} // 用于通知上下文的结束
    children map[canceler]struct{} // 子上下文集合
    err      error // 上下文的结束原因
    cause    error // 上下文的取消原因
}

  • cancelCtx 是实现了取消功能的上下文结构体。
  • Context 字段保存了parent context
  • mu 是用于保护结构体成员的互斥锁。
  • done 是一个通道,用于通知上下文的结束。当上下文被取消时,该通道会被关闭。
  • children 是一个 canceler 类型的映射,用于保存该parent contextchildren context
  • err 是上下文的结束原因,用于标识上下文的取消或其他错误,如果是使用cancel()函数取消的,err为Canceled,new 出来的错误类型。
  • cause 是上下文的取消原因,用于标识导致上下文被取消的具体原因。
  • 该结构体为context.WithCancel() 生成的context的原型。
  1. timerCtx 结构体:
type timerCtx struct {
    cancelCtx
    timer *time.Timer // 用于定时的计时器
    deadline time.Time // 截止时间
}

  • timerCtx 是在 cancelCtx 的基础上实现了定时功能的上下文结构体。
  • cancelCtx 字段保存了parent context withCancel成员变量。
  • timer 是用于定时的 time.Timer 结构体,用于在指定时间后触发上下文的结束。
  • deadline 是截止时间,用于限制操作的执行时间。
  • 该结构体是context.WithDeadline()context.WithTimeout()的原型。
  1. valueCtx 结构体:
type valueCtx struct {
    Context
    key, val interface{} // 键值对
}

  • valueCtx 是实现了值传递功能的上下文结构体。
  • Context 字段保存了paremt context
  • keyval 是键值对,用于在上下文中传递值。
  • 该struct是 context.WithValue 函数创建的上下文的原型,不具备取消功能。

这些上下文结构体之间的关系如下:

  • emptyCtx 可以作为上下文树的根节点,作为其他上下文类型的基础。
  • cancelCtx 是在 emptyCtx 的基础上实现了取消功能,可以作为其他上下文类型的基础,包括 timerCtx
  • timerCtx 是在 cancelCtx 的基础上扩展实现了定时功能。
  • valueCtx 可以作为叶子节点的上下文,或者作为其他上下文类型的父上下文,用于传递值。
主要函数分析
  1. WithCancel
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
	c := withCancel(parent)
	return c, func() { c.cancel(true, Canceled, nil) }
}

该函数用于创建一个具备取消功能的上下文,并返回该上下文对象以及一个取消函数。
首先,调用 withCancel 函数创建一个带有取消功能的上下文 cwithCancel 函数是 cancelCtx 类型的构造函数,用于创建一个取消上下文。
然后,通过匿名函数创建一个取消函数,该函数内部调用 c.cancel 方法,将上下文标记为取消状态,设置错误为 Canceled,并传入 nil 作为取消原因。
最后,返回具备取消功能的上下文对象 c 和取消函数。通过调用 context.WithCancel 函数创建的上下文可以通过调用返回的取消函数来取消上下文,从而通知与该上下文关联的代码停止执行。
2. WithCancelCause

func WithCancelCause(parent Context) (ctx Context, cancel CancelCauseFunc) {
	c := withCancel(parent)
	return c, func(cause error) { c.cancel(true, Canceled, cause) }
}

该函数用于创建一个具备取消功能的上下文,并返回该上下文对象以及一个带有取消原因的取消函数。
首先,调用 withCancel 函数创建一个带有取消功能的上下文 cwithCancel 函数是 cancelCtx 类型的构造函数,用于创建一个取消上下文。
然后,通过匿名函数创建一个取消函数,该函数接受一个取消原因作为参数。在函数内部,调用 c.cancel 方法,将上下文标记为取消状态,设置错误为 Canceled,并将传入的取消原因作为附加的取消信息。
最后,返回具备取消功能的上下文对象 c 和取消函数。与WithCancel的区别就是调用CancalFunc函数时带有cause参数。

通过调用 context.WithCancelCause 函数创建的上下文可以通过调用返回的取消函数来取消上下文,并提供取消的原因信息,以便向与该上下文关联的代码传递额外的取消信息。

  1. withCancel
func withCancel(parent Context) *cancelCtx {
        // 检查parent是否为nil,要保证parent非nil的前提下创建children context
	if parent == nil {
		panic("cannot create context from nil parent")
	}
        // 实例化一个CancelCtx
	c := newCancelCtx(parent)
        // 通过propagateCancel函数确保在parent context被取消时,children context也被取消
	propagateCancel(parent, c)
	return c
}

该函数用于创建一个具备取消功能的上下文。
首先,函数检查父上下文 parent 是否为 nil,如果为 nil,则抛出一个运行时错误。
然后,调用 newCancelCtx 函数创建一个新的取消上下文 cnewCancelCtxcancelCtx 类型的构造函数,用于创建一个取消上下文。接下来,通过调用 propagateCancel 函数,将父上下文 parent 和子上下文 c 相关联,以确保当父上下文被取消时,子上下文也会被取消(如果父上下文是不具备取消的,该函数不会做任何设置,直接就return)。
最后,返回具备取消功能的上下文对象 c。通过调用 withCancel 函数创建的上下文可以使用返回的上下文对象进行取消操作。当调用父上下文的取消函数时,将同时取消所有与之相关联的子上下文。
4. propagateCancel

// propagateCancel arranges for child to be canceled when parent is.
func propagateCancel(parent Context, child canceler) {
	// only background or todo context use Done() return nil
	done := parent.Done()
	if done == nil {
		return // parent is never canceled
	}

	select {
	case <-done:
		// parent is already canceled
		// removeFromParent is false,bacause child not yet added to parent.children[] Map
		child.cancel(false, parent.Err(), Cause(parent))
		return
	default:
	}
        // check the parent context is cancel
	if p, ok := parentCancelCtx(parent); ok {
		p.mu.Lock()
		if p.err != nil {
			// parent has already been canceled
			child.cancel(false, p.err, p.cause)
		} else {
                        // Map p.children is nil,make a new Map
			if p.children == nil {
				p.children = make(map[canceler]struct{})
			}
                        // store children context to parent.children map
			p.children[child] = struct{}{}
		}
		p.mu.Unlock()
	} else {
                // Parent context does not have cancellation function
		goroutines.Add(1)
		go func() {
			select {
			case <-parent.Done():
				child.cancel(false, parent.Err(), Cause(parent))
			case <-child.Done():
			}
		}()
	}
}

该函数用于在父上下文被取消时,传播取消信号给子上下文。

首先,函数通过调用 parent.Done() 方法获取父上下文的通道 done,如果该通道为 nil,则表示父上下文不会被取消,直接返回。

接着,使用 select 语句对 done 通道进行选择操作。如果父上下文已经被取消,即从 done 通道接收到值,那么调用 child.cancel 方法取消子上下文,并将父上下文的错误和取消原因传递给子上下文。如果父上下文尚未被取消,则进入下一个分支。函数检查父上下文是否为 cancelCtx 类型的上下文(即具备取消功能的上下文)。如果是,则加锁操作,并根据父上下文的状态来决定如何处理子上下文。如果父上下文已经被取消,直接调用 child.cancel 方法取消子上下文;否则,将子上下文添加到父上下文的 children 映射中。如果父上下文不是具备取消功能的上下文,即父上下文为 backgroundCtxtodoCtx,则启动一个新的 goroutine 来监视父上下文的取消状态和子上下文的取消状态。一旦父上下文被取消,或者子上下文被取消,就调用 child.cancel 方法取消子上下文。

通过 propagateCancel 函数,实现了当父上下文被取消时,自动取消与之相关联的子上下文。

  1. parentCancelCtx
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
	done := parent.Done()
	if done == closedchan || done == nil {
		return nil, false
	}
	p, ok := parent.Value(&cancelCtxKey).(*cancelCtx) // return parent context itself
        /*
            func (c *cancelCtx) Value(key any) any {
	        if key == &cancelCtxKey {
		return c
	        }
	        return value(c.Context, key)
            }
        */
	if !ok {
		return nil, false
	}
	pdone, _ := p.done.Load().(chan struct{})
	if pdone != done {
		return nil, false
	}
	return p, true
}

首先,函数通过调用 parent.Done() 方法获取父上下文的 done 通道。如果该通道是 closedchannil,则表示父上下文已经取消或者不具备取消功能,直接返回 nilfalse

接着,函数使用 parent.Value(&cancelCtxKey) 获取父上下文中键为 cancelCtxKey 的值,即取消上下文。然后将该值转换为 cancelCtx 类型,并判断是否转换成功。如果转换失败,表示父上下文不具备取消上下文,直接返回 nilfalse

继续,函数通过调用 p.done.Load() 获取取消上下文中的 done 通道,并将其转换为 chan struct{} 类型。然后将该通道与父上下文中的 done 通道进行比较。如果二者不相等,表示取消上下文与父上下文的 done 通道不一致,直接返回 nilfalse

最后,如果上述检查都通过,表示成功获取到父上下文的取消上下文,将取消上下文和布尔值 true 返回。

通过 parentCancelCtx 函数,可以判断父上下文是否具备取消上下文,并获取父上下文中的取消上下文对象

  1. 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{})
}

该方法用于获取取消上下文的 done 通道(用于接收取消信号)。
首先,方法通过调用 c.done.Load() 加载取消上下文的 done 通道。如果该通道不为 nil,则直接返回。

接着,方法获取互斥锁 c.mu,确保对共享数据的访问是线程安全的。然后再次调用 c.done.Load() 加载 done 通道,进行二次检查。

如果 done 通道仍然为 nil,表示尚未创建,于是通过 make(chan struct{}) 创建一个新的 done 通道,并将其存储到 c.done 中。

最后,返回 done 通道。

  1. 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 = err
	c.cause = cause
	d, _ := c.done.Load().(chan struct{})
	if d == nil {
		c.done.Store(closedchan)
	} else {
		close(d) // done is not nil,context already canceled,close channel
	}
        // cancel children of the context canceled
	for child := range c.children {
		// NOTE: acquiring the child's lock while holding parent's lock.
		child.cancel(false, err, cause)
	}
        // clear children map
	c.children = nil
	c.mu.Unlock()
        // remove context of parent children map
	if removeFromParent {
		removeChild(c.Context, c)
	}
}

该方法用于取消上下文,并传播取消信号给与之相关联的子上下文。

首先,方法对传入的取消错误 err 进行判断,如果为 nil,则抛出运行时错误。

接着,判断传入的取消原因 cause 是否为 nil,如果为 nil,则将 cause 设置为 err

然后,获取互斥锁 c.mu,确保对共享数据的访问是线程安全的。检查取消上下文的错误状态 c.err,如果已经被取消,则直接解锁并返回,避免重复取消。

如果取消上下文尚未被取消,则将错误状态 c.err 设置为传入的取消错误 err,并将取消原因 cause 存储在 c.cause 中。

接着,通过调用 c.done.Load() 加载 done 通道,进行判断。如果 done 通道为 nil,则将 closedchan 存储到 c.done 中表示取消已完成;否则,通过调用 close(d) 关闭 done 通道。

然后,遍历取消上下文的子上下文集合 c.children,通过递归调用 child.cancel 方法,取消子上下文,并传递相同的取消错误 err 和取消原因 cause

接下来,清空取消上下文的子上下文集合 c.children

最后,解锁互斥锁 c.mu。如果 removeFromParenttrue,则从父上下文中移除当前上下文。

  1. WithTimeout
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
	return WithDeadline(parent, time.Now().Add(timeout))
}

该函数接受一个parent context和一个超时时间 timeout ,调用 WithDeadline 函数创建一个具备截至时间的context,所以可以理解WithTimeout的本质就是WithDeadline,只是截至时间是当前时间加上timeout时间。

  1. WithDeadline
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
	if parent == nil {
		panic("cannot create context from nil parent")
	}
        // Check if the parent has a deadline. If so, cur is the parent context deadline, ok==true. If the parent's deadline is earlier than the deadline that the child is about to set, the child does not need to have a deadline, as the parent must timeout first. Simply use WithCancel
	if cur, ok := parent.Deadline(); ok && cur.Before(d) {
		// The current deadline is already sooner than the new one.
		// The deadline of parent context is beforer than the deadline of chilren context,so chilren context not need deadline
		return WithCancel(parent)
	}
        // If the parent deadline is later than or equal to the children deadline, create a timerCtx context object that contains a canceling context infrastructure called cancelCtx and a deadline deadline deadline
	c := &timerCtx{
		cancelCtx: newCancelCtx(parent),
		deadline:  d,
	}
        // By calling the propagateCancel function, associate the parent context with the child context so that when the parent context is cancelled, the child context will also be cancelled
	propagateCancel(parent, c)

        // Calculate how much time is left until the deadline and determine if the deadline has been exceeded. If the deadline has been exceeded, immediately cancel the context and return the context object with cancellation function and corresponding cancellation function
	dur := time.Until(d)
	if dur <= 0 {
		c.cancel(true, DeadlineExceeded, nil) // deadline has already passed
		return c, func() { c.cancel(false, Canceled, nil) }
	}
	c.mu.Lock()
	defer c.mu.Unlock()

        // If the deadline has not yet arrived and the context has not been cancelled, create a timer and trigger the cancellation operation after the specified time
	if c.err == nil {
		c.timer = time.AfterFunc(dur, func() {
			c.cancel(true, DeadlineExceeded, nil)
		})
	}
        // return context and CancelFunc
	return c, func() { c.cancel(true, Canceled, nil) }
}

该函数根据给定的截止时间 d 创建一个具备截止时间功能的上下文。

首先,函数对参数进行了判断,确保父上下文不为 nil。然后检查父上下文的截止时间,如果当前截止时间已经早于新的截止时间,则返回一个具备取消功能的父上下文(使用 WithCancel 函数)。

如果父上下文的截止时间晚于或等于新的截止时间,则创建一个 timerCtx 上下文对象,其中包含一个取消上下文的基础结构体 cancelCtx 和截止时间 deadline

接下来,通过调用 propagateCancel 函数,将父上下文和子上下文相关联,以便在父上下文取消时,也会取消子上下文。

然后,计算距离截止时间还有多长时间,并判断是否已经超过截止时间。如果已经超过截止时间,则立即取消上下文,并返回具备取消功能的上下文对象和相应的取消函数。

如果截止时间尚未到达,就创建一个计时器,并在指定的时间后触发取消操作。

最后,返回具备截止时间功能的上下文对象和取消函数

  1. WithVaule
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}
}

该函数用于创建一个具备键值对特性的上下文。

首先,函数检查父上下文 parent 是否为 nil,如果为 nil,则抛出一个运行时错误。

然后,函数检查键 key 是否为 nil,如果为 nil,则抛出一个运行时错误。

接着,函数使用 reflectlite.TypeOf(key) 获取键 key 的类型,并通过 Comparable() 方法判断键是否是可比较的类型。如果键不是可比较的类型,即不支持比较操作,那么抛出一个运行时错误(这里的key的类型必须是可比较类型)。

最后,创建一个新的 valueCtx 上下文,该上下文包含了父上下文 parent、键 key 和对应的值 val

你可能感兴趣的:(goland,golang,开发语言,后端)