经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Go语言 » 查看文章
对Go语言中的context包源码分析
来源:jb51  时间:2022/2/9 11:18:09  对本文有异议

一、包说明分析

context包:这个包分析的是1.15

context包定义了一个Context类型(接口类型),通过这个Context接口类型, 就可以跨api边界/跨进程传递一些deadline/cancel信号/request-scoped值.

发给server的请求中需要包含Context,server需要接收Context. 在整个函数调用链中,Context都需要进行传播. 期间是可以选择将Context替换为派生Context(由With-系列函数生成). 当一个Context是canceled状态时,所有派生的Context都是canceled状态.

With-系列函数(不包含WithValue)会基于父Context来生成一个派生Context, 还有一个CancelFunc函数,调用这个CancelFun函数可取消派生对象和 "派生对象的派生对象的...",并且会删除父Context和派生Context的引用关系, 最后还会停止相关定时器.如果不调用CancelFunc,直到父Context被取消或 定时器触发,派生Context和"派生Context的派生Context..."才会被回收, 否则就是泄露leak. go vet工具可以检测到泄露.

使用Context包的程序需要遵循以下以下规则,目的是保持跨包兼容, 已经使用静态分析工具来检查context的传播:

  • Context不要存储在struct内,直接在每个函数中显示使用,作为第一个参数,名叫ctx
  • 即使函数允许,也不要传递nil Context,如果实在不去确定就传context.TODO
  • 在跨进程和跨api时,要传request-scoped数据时用context Value,不要传函数的可选参数
  • 不同协程可以传递同一Context到函数,多协程并发使用Context是安全的

二、包结构分析

核心的是:

  1. ? ? func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
  2. ? ? func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
  3. ? ? func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
  4. ? ? type CancelFunc
  5. ? ? type Context

从上可以看出,核心的是Context接口类型,围绕这个类型出现了With-系列函数, 针对派生Context,还有取消函数CancelFunc.

还有两个暴露的变量:

Canceled

context取消时由Context.Err方法返回

DeadlineExceeded

context超过deadline时由Context.Err方法返回

三、Context接口类型分析

context也称上下文.

  1. type Context interface {
  2. ? ? ? Deadline() (deadline time.Time, ok bool)
  3. ? ? ? Done() <-chan struct{}
  4. ? ? ? Err() error
  5. ? ? ? Value(key interface{}) interface{}
  6. ? ? }

先看说明:

跨api时,Context可以携带一个deadline/一个取消信号/某些值. 并发安全.

方法集分析:

Deadline

  • 返回的是截至时间
  • 这个时间表示的是任务完成时间
  • 到这个时间点,Context的状态已经是be canceled(完成状态)
  • ok为false表示没有设置deadline
  • 连续调用,返回的结果是相同的

Done

  • 返回的只读信道
  • 任务完成,信道会被关闭,Context状态是be canceled
  • Conetext永远不be canceled,Done可能返回nil
  • 连续调用,返回的结果是相同的
  • 信道的关闭会异步发生,且会在取消函数CancelFunc执行完之后发生
  • 使用方面,Done需要配合select使用
  • 更多使用Done的例子在这个博客

Err

  • Done还没关闭(此处指Done返回的只读信道),Err返回nil
  • Done关闭了,Err返回non-nil的error
  • Context是be canceled,Err返回Canceled(这是之前分析的一个变量)
  • 如果是超过了截至日期deadline,Err返回DeadlineExceeded
  • 如果Err返回non-nil的error,后续再次调用,返回的结果是相同的

Value

  • 参数和返回值都是interface{}类型(这种解耦方式值得学习)
  • Value就是通过key找value,如果没找到,返回nil
  • 连续调用,返回的结果是相同的
  • 上下文值,只适用于跨进程/跨api的request-scoped数据
  • 不适用于代替函数可选项
  • 一个上下文中,一个key对应一个value
  • 典型用法:申请一个全局变量来放key,在context.WithValue/Context.Value中使用
  • key应该定义为非暴露类型,避免冲突
  • 定义key时,应该支持类型安全的访问value(通过key)
  • key不应该暴露
    • 表示应该通过暴露函数来进行隔离(具体可以查看源码中的例子)

四、后续分析规划

看完Context的接口定义后,还需要查看With-系列函数才能知道context的定位, 在With-系列中会涉及到Context的使用和内部实现,那就先看WithCancel.

withCancel:

  • CancelFunc
  • newCancelCtx
    • cancelCtx
  • canceler
  • propagateCancel
    • parentCancelCtx

以下是分析出的通过规则:很多包对外暴露的是接口类型和几个针对此类型的常用函数. 接口类型暴露意味可扩展,但是想扩展之后继续使用常用函数,那扩展部分就不能 修改常用函数涉及的部分,当然也可以通过额外的接口继续解耦. 针对"暴露接口和常用函数"这种套路,实现时会存在一个非暴露的实现类型, 常用函数就是基于这个实现类型实现的.在context.go中的实现类型是emptyCtx. 如果同时需要扩展接口和常用函数,最好是重新写一个新包.

下面的分析分成两部分:基于实现类型到常用函数;扩展功能以及如何扩展.

五、基于实现类型到常用函数

Context接口的实现类型是emptyCtx. 

  1. ? type emptyCtx int
  2. ? ? func (*emptyCtx) Deadline() (deadline time.Time, ok bool) { return }
  3. ? ? func (*emptyCtx) Done() <-chan struct{} { return nil }
  4. ? ? func (*emptyCtx) Err() error { return nil }
  5. ? ? func (*emptyCtx) Value(key interface{}) interface{} { return nil }
  6.  
  7. ? ? func (e *emptyCtx) String() string {
  8. ? ? ? switch e {
  9. ? ? ? case background:
  10. ? ? ? ? return "context.Background"
  11. ? ? ? case todo:
  12. ? ? ? ? return "context.TODO"
  13. ? ? ? }
  14. ? ? ? return "unknown empty Context"
  15. ? ? }

可以看到emptyCtx除了实现了context.Context接口,还实现了context.stringer接口, 注意下面的String不是实现的fmt.Stringer接口,而是未暴露的context.stringer接口. 正如empty的名字,对Context接口的实现都是空的,后续需要针对emptyCtx做扩展.

  1. ? ? var (
  2. ? ? ? background = new(emptyCtx)
  3. ? ? ? todo ? ? ? = new(emptyCtx)
  4. ? ? )
  5. ? ? func Background() Context {
  6. ? ? ? return background
  7. ? ? }
  8. ? ? func TODO() Context {
  9. ? ? ? return todo
  10. ? ? }

这里通过两个暴露的函数创建两个空的emptyCtx实例,后续会根据不同场景来扩展. 在注释中,background实例的使用场景是:main函数/初始化/测试/或者作为top-level 的Context(派生其他Context);todo实例的使用场景是:不确定时用todo. 到此emptyCtx的构造就理顺了,就是Background()/TODO()两个函数,之后是针对她们 的扩展和Context派生.

Context派生是基于With-系列函数实现的,我们先看对emptyCtx的扩展, 这些扩展至少会覆盖一部分函数,让空的上下文变成支持某些功能的上下文, 取消信号/截至日期/值,3种功能的任意组合.

从源码中可以看出,除了emptyCtx,还有cancelCtx/myCtx/myDoneCtx/otherContext/ timeCtx/valueCtx,他们有个共同特点:基于Context组合的新类型, 我们寻找的对emptyCtx的扩展,就是在这些新类型的方法中.

小技巧:emptyCtx已经实现了context.Context,如果要修改方法的实现, 唯一的方法就是利用Go的内嵌进行方法的覆盖.简单点说就是内嵌到struct, struct再定义同样签名的方法,如果不需要数据,内嵌到接口也是一样的.

cancelCtx

支持取消信号的上下文

  1. type cancelCtx struct {
  2. ? Context
  3.  
  4. ? mu ? ? ? sync.Mutex
  5. ? done ? ? chan struct{}
  6. ? children map[canceler]struct{}
  7. ? err ? ? ?error
  8. }

看下方法:

  1. ? var cancelCtxKey int
  2. ? ? func (c *cancelCtx) Value(key interface{}) interface{} {
  3. ? ? ? if key == &cancelCtxKey {
  4. ? ? ? ? return c
  5. ? ? ? }
  6. ? ? ? return c.Context.Value(key)
  7. ? ? }
  8. ? ? func (c *cancelCtx) Done() <-chan struct{} {
  9. ? ? ? c.mu.Lock()
  10. ? ? ? if c.done == nil {
  11. ? ? ? ? c.done = make(chan struct{})
  12. ? ? ? }
  13. ? ? ? d := c.done
  14. ? ? ? c.mu.Unlock()
  15. ? ? ? return d
  16. ? ? }
  17. ? ? func (c *cancelCtx) Err() error {
  18. ? ? ? c.mu.Lock()
  19. ? ? ? err := c.err
  20. ? ? ? c.mu.Unlock()
  21. ? ? ? return err
  22. ? ? }

cancelCtxKey默认是0,Value()要么返回自己,要么调用上下文Context.Value(), 具体使用后面再分析;Done()返回cancelCtx.done;Err()返回cancelCtx.err;

  1. ? ? func contextName(c Context) string {
  2. ? ? ? if s, ok := c.(stringer); ok {
  3. ? ? ? ? return s.String()
  4. ? ? ? }
  5. ? ? ? return reflectlite.TypeOf(c).String()
  6. ? ? }
  7. ? ? func (c *cancelCtx) String() string {
  8. ? ? ? return contextName(c.Context) + ".WithCancel"
  9. ? ? }

internal/reflectlite.TypeOf是获取接口动态类型的反射类型, 如果接口是nil就返回nil,此处是获取Context的类型, 从上面的分析可知,顶层Context要么是background,要么是todo, cancelCtx实现的context.stringer要么是context.Background.WithCancel, 要么是context.TODO.WithCancel.这里说的只是顶层Context下的, 多层派生Context的结构也是类似的.

值得注意的是String()不属于Context接口的方法集,而是emptyCtx对 context.stringer接口的实现,cancelCxt内嵌的Context,所以不会覆盖 emptyCtx对String()的实现. 

  1. ? var closedchan = make(chan struct{})
  2. ? ? func init() {
  3. ? ? ? close(closedchan)
  4. ? ? }
  5.  
  6. ? ? func (c *cancelCtx) cancel(removeFromParent bool, err error) {
  7. ? ? ? if err == nil {
  8. ? ? ? ? panic("context: internal error: missing cancel error")
  9. ? ? ? }
  10. ? ? ? c.mu.Lock()
  11. ? ? ? if c.err != nil {
  12. ? ? ? ? c.mu.Unlock()
  13. ? ? ? ? return // already canceled
  14. ? ? ? }
  15. ? ? ? c.err = err
  16. ? ? ? if c.done == nil {
  17. ? ? ? ? c.done = closedchan
  18. ? ? ? } else {
  19. ? ? ? ? close(c.done)
  20. ? ? ? }
  21. ? ? ? for child := range c.children {
  22. ? ? ? ? // NOTE: acquiring the child's lock while holding parent's lock.
  23. ? ? ? ? child.cancel(false, err)
  24. ? ? ? }
  25. ? ? ? c.children = nil
  26. ? ? ? c.mu.Unlock()
  27.  
  28. ? ? ? if removeFromParent {
  29. ? ? ? ? removeChild(c.Context, c)
  30. ? ? ? }
  31. ? ? }

cancel(),具体的取消信令对应的操作,err不能为nil,err会存到cancelCtx.err, 如果已经存了,表示取消操作已经执行.关闭done信道,如果之前没有调用Done() 来获取done信道,就返回一个closedchan(这是要给已关闭信道,可重用的), 之后是调用children的cancel(),最后就是在Context树上移除当前派生Context.

  1. ? ? func parentCancelCtx(parent Context) (*cancelCtx, bool) {
  2. ? ? ? done := parent.Done()
  3. ? ? ? if done == closedchan || done == nil {
  4. ? ? ? ? return nil, false
  5. ? ? ? }
  6. ? ? ? p, ok := parent.Value(&cancelCtxKey).(*cancelCtx)
  7. ? ? ? if !ok {
  8. ? ? ? ? return nil, false
  9. ? ? ? }
  10. ? ? ? p.mu.Lock()
  11. ? ? ? ok = p.done == done
  12. ? ? ? p.mu.Unlock()
  13. ? ? ? if !ok {
  14. ? ? ? ? return nil, false
  15. ? ? ? }
  16. ? ? ? return p, true
  17. ? ? }
  18. ? ? func removeChild(parent Context, child canceler) {
  19. ? ? ? p, ok := parentCancelCtx(parent)
  20. ? ? ? if !ok {
  21. ? ? ? ? return
  22. ? ? ? }
  23. ? ? ? p.mu.Lock()
  24. ? ? ? if p.children != nil {
  25. ? ? ? ? delete(p.children, child)
  26. ? ? ? }
  27. ? ? ? p.mu.Unlock()
  28. ? ? }

removeChild首先判断父Context是不是cancelCtx类型, 再判断done信道和当前Context的done信道是不是一致的, (如果不一致,说明:done信道是diy实现的,就不能删掉了).

到此,cancelCtx覆盖了cancelCtx.Context的Done/Err/Value, 同时实现了自己的打印函数String(),还实现了cancel(). 也就是说cancelCtx还实现了接口canceler:

  1. type canceler interface {
  2. ? cancel(removeFromParent bool, err error)
  3. ? Done() <-chan struct{}
  4. }
  5. // cancelCtx.children的定义如下:
  6. // children map[canceler]struct{}

执行取消信号对应的操作时,其中有一步就是执行children的cancel(), children的key是canceler接口类型,所以有对cancel()的实现. cancelCtx实现了canceler接口,那么在派生Context就可以嵌套很多层, 或派生很多个cancelCtx.

  1. func newCancelCtx(parent Context) cancelCtx {
  2. ? return cancelCtx{Context: parent}
  3. }

非暴露的构造函数.

回顾一下:cancelCtx添加了Context对取消信号的支持. 只要触发了"取消信号",使用方只需要监听done信道即可.

myCtx myDoneCtx otherContext属于测试,等分析测试的时候再细说.

timerCtx

前面说到了取消信号对应的上下文cancelCtx,timerCtx就是基于取消信号上下扩展的

  1. type timerCtx struct {
  2. ? cancelCtx
  3. ? timer *time.Timer
  4.  
  5. ? deadline time.Time
  6. }

注释说明:内嵌cancelCtx是为了复用Done和Err,扩展了一个定时器和一个截至时间, 在定时器触发时触发cancelCtx.cancel()即可.

  1. ? func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
  2. ? ? ? return c.deadline, true
  3. ? ? }
  4. ? ? func (c *timerCtx) String() string {
  5. ? ? ? return contextName(c.cancelCtx.Context) + ".WithDeadline(" +
  6. ? ? ? ? c.deadline.String() + " [" +
  7. ? ? ? ? time.Until(c.deadline).String() + "])"
  8. ? ? }
  9. ? ? func (c *timerCtx) cancel(removeFromParent bool, err error) {
  10. ? ? ? c.cancelCtx.cancel(false, err)
  11. ? ? ? if removeFromParent {
  12. ? ? ? ? removeChild(c.cancelCtx.Context, c)
  13. ? ? ? }
  14. ? ? ? c.mu.Lock()
  15. ? ? ? if c.timer != nil {
  16. ? ? ? ? c.timer.Stop()
  17. ? ? ? ? c.timer = nil
  18. ? ? ? }
  19. ? ? ? c.mu.Unlock()
  20. ? ? }

timerCtx内嵌了cancelCtx,说明timerCtx也实现了canceler接口, 从源码中可以看出,cancel()是重新实现了,String/Deadline都重新实现了.

cancel()中额外添加了定时器的停止操作.

这里没有deadline设置和定时器timer开启的操作,会放在With-系列函数中.

回顾一下: Context的deadline是机会取消信号实现的.

valueCtx

valueCtx和timerCtx不同,是直接基于Context的.

  1. type valueCtx struct {
  2. ? Context
  3. ? key, val interface{}
  4. }

一个valueCtx附加了一个kv对.实现了ValueString.

  1. ? func stringify(v interface{}) string {
  2. ? ? ? switch s := v.(type) {
  3. ? ? ? case stringer:
  4. ? ? ? ? return s.String()
  5. ? ? ? case string:
  6. ? ? ? ? return s
  7. ? ? ? }
  8. ? ? ? return "<not Stringer>"
  9. ? ? }
  10. ? ? func (c *valueCtx) String() string {
  11. ? ? ? return contextName(c.Context) + ".WithValue(type " +
  12. ? ? ? ? reflectlite.TypeOf(c.key).String() +
  13. ? ? ? ? ", val " + stringify(c.val) + ")"
  14. ? ? }
  15. ? ? func (c *valueCtx) Value(key interface{}) interface{} {
  16. ? ? ? if c.key == key {
  17. ? ? ? ? return c.val
  18. ? ? ? }
  19. ? ? ? return c.Context.Value(key)
  20. ? ? }

因为valueCtx.val类型是接口类型interface{},所以获取具体值时, 使用了switch type.

六、With-系列函数

支持取消信号 WithCancel:

  1. var Canceled = errors.New("context canceled")
  2. func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
  3. ? if parent == nil {
  4. ? ? panic("cannot create context from nil parent")
  5. ? }
  6. ? c := newCancelCtx(parent)
  7. ? propagateCancel(parent, &c)
  8. ? return &c, func() { c.cancel(true, Canceled) }
  9. }

派生一个支持取消信号的Context,类型是cancelCtx,CancelFunc是取消操作, 具体是调用cancelCtx.cancel()函数,err参数是Canceled.

  1. ? ? func propagateCancel(parent Context, child canceler) {
  2. ? ? ? done := parent.Done()
  3. ? ? ? if done == nil {
  4. ? ? ? ? return // parent is never canceled
  5. ? ? ? }
  6.  
  7. ? ? ? select {
  8. ? ? ? case <-done:
  9. ? ? ? ? // parent is already canceled
  10. ? ? ? ? child.cancel(false, parent.Err())
  11. ? ? ? ? return
  12. ? ? ? default:
  13. ? ? ? }
  14.  
  15. ? ? ? if p, ok := parentCancelCtx(parent); ok {
  16. ? ? ? ? p.mu.Lock()
  17. ? ? ? ? if p.err != nil {
  18. ? ? ? ? ? // parent has already been canceled
  19. ? ? ? ? ? child.cancel(false, p.err)
  20. ? ? ? ? } else {
  21. ? ? ? ? ? if p.children == nil {
  22. ? ? ? ? ? ? p.children = make(map[canceler]struct{})
  23. ? ? ? ? ? }
  24. ? ? ? ? ? p.children[child] = struct{}{}
  25. ? ? ? ? }
  26. ? ? ? ? p.mu.Unlock()
  27. ? ? ? } else {
  28. ? ? ? ? atomic.AddInt32(&goroutines, +1)
  29. ? ? ? ? go func() {
  30. ? ? ? ? ? select {
  31. ? ? ? ? ? case <-parent.Done():
  32. ? ? ? ? ? ? child.cancel(false, parent.Err())
  33. ? ? ? ? ? case <-child.Done():
  34. ? ? ? ? ? }
  35. ? ? ? ? }()
  36. ? ? ? }
  37. ? ? }

传播取消信号.如果父Context不支持取消信号,那就不传播. 如果父Context的取消信号已经触发(就是父Context的done信道已经触发或关闭), 之后判断父Context是不是cancelCtx,如果是就将此Context丢到children中, 如果父Context不是cancelCtx,那就起协程监听父子Context的done信道.

小技巧:

  1. select {
  2. case <-done:
  3. ? child.cancel(false, parent.Err())
  4. ? return
  5. default:
  6. }

不加default,会等到done信道有动作;加了会立马判断done信道,done没操作就结束select.

  1. select {
  2. case <-parent.Done():
  3. ? child.cancel(false, parent.Err())
  4. case <-child.Done():
  5. }

这个会等待,因为没有加default.

因为顶层Context目前只能是background和todo,不是cancelCtx, 所以顶层Context的直接派生Context不会触发propagateCancel中的和children相关操作, 至少得3代及以后才有可能.

WithCancel的取消操作会释放相关资源,所以在上下文操作完之后,最好尽快触发取消操作. 触发的方式是:done信道触发,要么有数据,要么被关闭.

支持截至日期 WithDeadline:

 

  1. ? func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
  2. ? ? ? if parent == nil {
  3. ? ? ? ? panic("cannot create context from nil parent")
  4. ? ? ? }
  5. ? ? ? if cur, ok := parent.Deadline(); ok && cur.Before(d) {
  6. ? ? ? ? // The current deadline is already sooner than the new one.
  7. ? ? ? ? return WithCancel(parent)
  8. ? ? ? }
  9. ? ? ? c := &timerCtx{
  10. ? ? ? ? cancelCtx: newCancelCtx(parent),
  11. ? ? ? ? deadline: ?d,
  12. ? ? ? }
  13. ? ? ? propagateCancel(parent, c)
  14. ? ? ? dur := time.Until(d)
  15. ? ? ? if dur <= 0 {
  16. ? ? ? ? c.cancel(true, DeadlineExceeded) // deadline has already passed
  17. ? ? ? ? return c, func() { c.cancel(false, Canceled) }
  18. ? ? ? }
  19. ? ? ? c.mu.Lock()
  20. ? ? ? defer c.mu.Unlock()
  21. ? ? ? if c.err == nil {
  22. ? ? ? ? c.timer = time.AfterFunc(dur, func() {
  23. ? ? ? ? ? c.cancel(true, DeadlineExceeded)
  24. ? ? ? ? })
  25. ? ? ? }
  26. ? ? ? return c, func() { c.cancel(true, Canceled) }
  27. ? ? }

With-系列函数用于生成派生Context,函数内部第一步都是判断父Context是否为nil, WithDeadline第二步是判断父Context是否支持deadline,支持就将取消信号传递给派生Context, 如果不支持,就为当前派生Context支持deadline.

先理一下思路,目前Context的实现类型有4个:emptyCtx/cancelCtx/timerCtx/valueCtx, 除了emptyCtx,实现Deadline()方法的只有timerCtx,(ps:这里的实现特指有业务意义的实现), 唯一可以构造timerCtx的只有WithDeadline的第二步中. 这么说来,顶层Context不支持deadline,最多第二层派生支持deadline的Context, 第三层派生用于将取消信号进行传播.

WithCancel上面已经分析了,派生一个支持取消信号的Context,并将父Context的取消信号 传播到派生Context(ps:这么说有点绕,简单点讲就是将派生Context添加到父Context的children), 下面看看第一个构造支持deadline的过程.

构造timerCtx,传播取消信号,判断截至日期是否已过,如果没过,利用time.AfterFunc创建定时器, 设置定时触发的协程处理,之后返回派生Context和取消函数.

可以看到,整个WithDeadline是基于WithCancel实现的,截至日期到期后,利用取消信号来做后续处理.

因为timerCtx是内嵌了cancelCtx,所以有一个派生Context是可以同时支持取消和deadline的, 后面的value支持也是如此.

WithDeadline的注释说明: 派生Context的deadline不晚于参数,如果参数晚于父Context支持的deadline,使用父Context的deadline, 如果参数指定的比父Context早,或是父Context不支持deadline,那么派生Context会构造一个新的timerCtx. 父Context的取消/派生Context的取消/或者deadline的过期,都会触发取消信号对应的操作执行, 具体就是Done()信道会被关闭.

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

WitchTimeout是基于WithDeadline实现的,是一种扩展,从设计上可以不加,但加了会增加调用者的便捷. WithTimeout可用在"慢操作"上.上下文使用完之后,应该立即调用取消操作来释放资源.

支持值WitchValue:

  1. func WithValue(parent Context, key, val interface{}) Context {
  2. ? if parent == nil {
  3. ? ? panic("cannot create context from nil parent")
  4. ? }
  5. ? if key == nil {
  6. ? ? panic("nil key")
  7. ? }
  8. ? if !reflectlite.TypeOf(key).Comparable() {
  9. ? ? panic("key is not comparable")
  10. ? }
  11. ? return &valueCtx{parent, key, val}
  12. }

只要是key能比较,就构造一个valueCtx,用Value()获取值时,如果和当前派生Context的key不匹配, 就会和父Context的key做匹配,如果不匹配,最后顶层Context会返回nil.

总结一下:如果是Value(),会一直通过派生Context找到顶层Context; 如果是deadline,会返回当前派生Context的deadline,但会受到父Context的deadline和取消影响; 如果是取消函数,会将传播取消信号的相关Context都做取消操作. 最重要的是Context是一个树形结构,可以组成很复杂的结构.

到目前为止,只了解了包的内部实现(顶层Context的构造/With-系列函数的派生), 具体使用,需要看例子和实际测试.

ps:一个包内部如何复杂,对外暴露一定要简洁.一个包是无法设计完美的,但是约束可以, 当大家都接受一个包,并接受使用包的规则时,这个包就成功了,context就是典型.

对于值,可以用WithValue派生,用Value取; 对于cancel/deadline,可以用WithDeadline/WithTimeout派生,通过Done信号获取结束信号, 也可以手动用取消函数来触发取消操作.整个包的功能就这么简单.

七、扩展功能以及如何扩展

扩展功能现在支持取消/deadline/value,扩展这个层级不应该放在这个包, 扩展Context,也就是新建Context的实现类型,这个是可以的, 同样实现类型需要承载扩展功能,也不合适.

  1. type canceler interface {
  2. ? cancel(removeFromParent bool, err error)
  3. ? Done() <-chan struct{}
  4. }

接口canceler是保证取消信号可以在链上传播,cancel方法由cancelCtx/timerCtx实现, Done只由cancelCtx创建done信道,不管是从功能上还是方法上都没有扩展的必要.

剩下的就是Value扩展成多kv对,这个主要还是要看应用场景.

八、补充

Context被取消后Err返回Canceled错误,超时之后Err返回DeadlineExceeded错误, 这个DeadlineExceeded还有些说法: 

  1. var DeadlineExceeded error = deadlineExceededError{}
  2.  
  3. ? type deadlineExceededError struct{}
  4. ? func (deadlineExceededError) Error() string {
  5. ? ? return "context deadline exceeded"
  6. ? }
  7. ? func (deadlineExceededError) Timeout() bool ? { return true }
  8. ? func (deadlineExceededError) Temporary() bool { return true }

再看看net.Error接口:

  1. type Error interface {
  2. ? error
  3. ? Timeout() bool ? // Is the error a timeout?
  4. ? Temporary() bool // Is the error temporary?
  5. }

context中的DeadlineExceeded默认是实现了net.Error接口的实例. 这个是为后面走网络超时留下的扩展.

到此这篇关于对Go语言中的context包源码分析的文章就介绍到这了,更多相关Go语言context包源码分析内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号