关于go:Go语言-context包源码学习

37次阅读

共计 13749 个字符,预计需要花费 35 分钟才能阅读完成。

你必须十分致力,能力看起来毫不费力!

微信搜寻公众号 [漫漫 Coding 路],一起 From Zero To Hero !

前言

日常 Go 开发中,Context 包是用的最多的一个了,简直所有函数的第一个参数都是 ctx,那么咱们为什么要传递 Context 呢,Context 又有哪些用法,底层实现是如何呢?置信你也肯定会有摸索的欲望,那么就跟着本篇文章,一起来学习吧!

需要一

开发中必定会调用别的函数,比方 A 调用 B,在调用过程中常常会设置超时工夫,比方超过 2s 就不期待 B 的后果了,间接返回,那么咱们须要怎么做呢?

// 睡眠 5s,模仿长时间操作
func FuncB() (interface{}, error) {time.Sleep(5 * time.Second)
    return struct{}{}, nil
}

func FuncA() (interface{}, error) {var res interface{}
    var err error
    ch := make(chan interface{})

  // 调用 FuncB(),将后果保留至 channel 中
    go func() {res, err = FuncB()
        ch <- res
    }()

  // 设置一个 2s 的定时器
    timer := time.NewTimer(2 * time.Second)
  
  // 监测是定时器先完结,还是 FuncB 先返回后果
    select {
    
    // 超时,返回默认值
    case <-timer.C:
        return "default", err
    
    // FuncB 先返回后果,敞开定时器,返回 FuncB 的后果
    case r := <-ch:
        if !timer.Stop() {<-timer.C}
        return r, err
    }

}

func main() {res, err := FuncA()
    fmt.Println(res, err)
}

下面咱们的实现,能够实现超过等待时间后,A 不期待 B,然而 B 并没有感触到勾销信号,如果 B 是个计算密度型的函数,咱们也心愿 B 感知到勾销信号,及时勾销计算并返回,缩小资源节约。

另一种状况,如果存在多层调用,比方 A 调用 B、C,B 调用 D、E,C 调用 E、F,在超过 A 的超时工夫后,咱们心愿勾销信号可能一层层的传递上来,后续所有被调用到的函数都能感知到,及时返回。

需要二

在多层调用的时候,A->B->C->D,有些数据须要固定传输,比方 LogID,通过打印雷同的 LogID,咱们就可能追溯某一次调用,不便问题的排查。如果每次都须要传参的话,未免太麻烦了,咱们能够应用 Context 来保留。通过设置一个固定的 Key,打印日志时从中取出 value 作为 LogID。

const LogKey = "LogKey"

// 模仿一个日志打印,每次从 Context 中取出 LogKey 对应的 Value 作为 LogID
type Logger struct{}
func (logger *Logger) info(ctx context.Context, msg string) {logId, ok := ctx.Value(LogKey).(string)
    if !ok {logId = uuid.New().String()}
    fmt.Println(logId + " " + msg)
}
var logger Logger

// 日志打印 并 调用 FuncB
func FuncA(ctx context.Context) {logger.info(ctx, "FuncA")
    FuncB(ctx)
}

func FuncB(ctx context.Context) {logger.info(ctx, "FuncB")
}

// 获取初始化的,带有 LogID 的 Context,个别在程序入口做
func getLogCtx(ctx context.Context) context.Context {logId, ok := ctx.Value(LogKey).(string)
    if ok {return ctx}
    logId = uuid.NewString()
    return context.WithValue(ctx, LogKey, logId)
}

func main() {ctx = getLogCtx(context.Background())
    FuncA(ctx)
}

这利用到了本篇文章讲到的 valueCtx,持续往下看,一起来学习 valueCtx 是怎么实现的吧!

Context 接口

type Context interface {Deadline() (deadline time.Time, ok bool)

    Done() <-chan struct{}

    Err() error

    Value(key interface{}) interface{}}

Context 接口比较简单,定义了四个办法:

  • Deadline() 办法返回两个值,deadline 示意 Context 将会在什么工夫点勾销,ok 示意是否设置了 deadline。当 ok=false 时,示意没有设置 deadline,那么此时 deadline 将会是个零值。屡次调用这个办法返回同样的后果。
  • Done() 返回一个只读的 channel,类型为 chan struct{},如果以后的 Context 不反对勾销,Done 返回 nil。咱们晓得,如果一个 channel 中没有数据,读取数据会阻塞;而如果 channel 被敞开,则能够读取到数据,因而能够监听 Done 返回的 channel,来获取 Context 勾销的信号。
  • Err() 返回 Done 返回的 channel 被敞开的起因。当 channel 未被敞开时,Err() 返回 nil;channel 被敞开时则返回相应的值,比方 Canceled、DeadlineExceeded。Err() 返回一个非 nil 值之后,前面再次调用会返回雷同的值。
  • Value() 返回 Context 保留的键值对中,key 对应的 value,如果 key 不存在则返回 nil。

Done() 是一个比拟罕用的办法,上面是一个比拟经典的流式解决工作的示例:监听 ctx.Done() 是否被敞开来判断工作是否须要勾销,须要勾销则返回相应的起因;没有勾销则将计算的后果写入到 out channel 中。

 func Stream(ctx context.Context, out chan<- Value) error {
     for {
    
    // 解决数据
         v, err := DoSomething(ctx)
         if err != nil {return err}
    
    // ctx.Done() 读取到数据,阐明获取到了工作勾销的信号
         select {case <-ctx.Done():
             return ctx.Err()
    // 否则将后果输入,持续计算
         case out <- v:
         }
     }
 }

Value() 也是一个比拟罕用的办法,用于在上下文中传递一些数据。应用 context.WithValue() 办法存入 key 和 value,通过 Value() 办法则能够依据 key 拿到 value。

func main() {ctx := context.Background()
    c := context.WithValue(ctx, "key", "value")
    v, ok := c.Value("key").(string)
    fmt.Println(v, ok)
}

emptyCtx

Context 接口并不需要咱们本人去手动实现,个别咱们都是间接应用 context 包中提供的 Background() 办法和 TODO() 办法,来获取最根底的 Context。

var (background = new(emptyCtx)
    todo       = new(emptyCtx)
)

func Background() Context {return background}

func TODO() Context {return todo}

Background() 办法个别用在 main 函数,或者程序的初始化办法中;在咱们不晓得应用哪个 Context,或者上文没有传递 Context 时,能够应用 TODO()。

Background() 和 TODO() 都是基于 emptyCtx 生成的,从名字能够看进去,emptyCtx 是一个空的 Context,没有 deadline、不能被勾销、没有键值对。

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}

func (e *emptyCtx) String() string {
    switch e {
    case background:
        return "context.Background"
    case todo:
        return "context.TODO"
    }
    return "unknown empty Context"
}

除了下面两个最根本的 Context 外,context 包中提供了性能更加丰盛的 Context,包含 valueCtx、cancelCtx、timerCtx,上面咱们就挨个来看下。

valueCtx

应用示例

咱们个别应用 context.WithValue() 办法向 Context 存入键值对,而后通过 Value() 办法依据 key 失去 value,此种性能的实现就依赖 valueCtx。

func main() {ctx := context.Background()
    c := context.WithValue(ctx, "myKey", "myValue")

    v1 := c.Value("myKey")
    fmt.Println(v1.(string))

    v2 := c.Value("hello")
    fmt.Println(v2) //  nil
}

类型定义

valueCtx 构造体中嵌套了 Context,应用 key、value 来保留键值对:

type valueCtx struct {
    Context
    key, val interface{}}

WithValue

context 包 对外裸露了 WithValue 办法,基于一个 parent context 来创立一个 valueCtx。从上面的源码中能够看出,key 必须是可比拟的!

func WithValue(parent Context, key, val interface{}) 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}
}

*valueCtx 实现了 Value(),能够依据 key 失去 value。这是一个向上递归寻找的过程,如果 key 不在以后 valueCtx 中,会持续向上找 parent Context,直到找到最顶层的 Context,个别最顶层的是 emptyCtx,而 emtpyCtx.Value() 返回 nil。

func (c *valueCtx) Value(key interface{}) interface{} {
    if c.key == key {return c.val}
    return c.Context.Value(key)
}

cancelCtx

cancelCtx 是一个用于勾销工作的 Context,工作通过监听 Context 是否被勾销,来决定是否持续解决工作还是间接返回。

如下示例中,咱们在 main 函数定义了一个 cancelCtx,并在 2s 后调用 cancel() 勾销 Context,即咱们心愿 doSomething() 在 2s 内实现工作,否则就能够间接返回,不须要再持续计算浪费资源了。

doSomething() 办法外部,咱们应用 select 监听工作是否实现,以及 Context 是否曾经勾销,哪个先到就执行哪个分支。办法模仿了一个 5s 的工作,main 函数等待时间是 2s,因而没有实现工作;如果 main 函数等待时间改为 10s,则工作实现并会返回后果。

这只是一层调用,真实情况下可能会有多级调用,比方 doSomething 可能又会调用其余工作,一旦 parent Context 勾销,后续的所有工作都应该勾销。

func doSomething(ctx context.Context) (interface{}, error) {res := make(chan interface{})
    go func() {fmt.Println("do something")
        time.Sleep(time.Second * 5)
        res <- "done"
    }()

    select {case <-ctx.Done():
        return nil, ctx.Err()
    case value := <-res:
        return value, nil
    }
}

func main() {ctx, cancel := context.WithCancel(context.Background())
    go func() {time.Sleep(time.Second * 2)
        cancel()}()
    res, err := doSomething(ctx)
    fmt.Println(res, err) // nil , context canceled
}

接下来就让咱们来钻研下,cancelCtx 是如何实现勾销的吧

类型定义

  • canceler 接口蕴含 cancel() 和 Done() 办法,cancelCtx 和 timerCtx 均实现了这个接口。
  • closedchan 是一个被敞开的 channel,能够用于前面 Done() 返回
  • canceled 是一个 err,用于 Context 被勾销的起因
type canceler interface {cancel(removeFromParent bool, err error)
    Done() <-chan struct{}
}

// closedchan is a reusable closed channel.
var closedchan = make(chan struct{})

func init() {close(closedchan)
}

var Canceled = errors.New("context canceled")

CancelFunc 是一个函数类型定义,是一个勾销函数,有如下标准:

  • CancelFunc 通知一个工作进行工作
  • CancelFunc 不会期待工作完结
  • CancelFunc 反对并发调用
  • 第一次调用后,后续的调用不会产生任何成果
type CancelFunc func()

&cancelCtxKey 是一个固定的 key,用来返回 cancelCtx 本身

var cancelCtxKey int

cancelCtx

cancelCtx 是能够被勾销的,它嵌套了 Context 接口,实现了 canceler 接口。cancelCtx 应用 children 字段保留同样实现 canceler 接口的子节点,当 cancelCtx 被勾销时,所有的子节点也会勾销。

type cancelCtx struct {
    Context

    mu       sync.Mutex            // 爱护如下字段,保障线程平安
    done     atomic.Value          // 保留 channel,懒加载,调用 cancel 办法时会敞开这个 channel
    children map[canceler]struct{} // 保留子节点,第一次调用 cancel 办法时会置为 nil
    err      error                 // 保留为什么被勾销,默认为 nil,第一次调用 cancel 会赋值
}

cancelCtx 的 Value() 办法 和 valueCtx 的 Value() 办法相似,只不过加了个固定的 key: &cancelCtxKey。当 key 为 &cancelCtxKey 时返回本身

func (c *cancelCtx) Value(key interface{}) interface{} {
    if key == &cancelCtxKey {return c}
    return c.Context.Value(key)
}

*cancelCtx 的 done 字段是懒加载的,只有在调用 Done() 办法 或者 cancel() 时才会赋值。

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

Err 办法返回 cancelCtx 的 err 字段

func (c *cancelCtx) Err() error {c.mu.Lock()
   err := c.err
   c.mu.Unlock()
   return err
}

WithCancel

那么咱们如何新建一个 cancelCtx 呢?context 包提供了 WithCancel() 办法,让咱们基于一个 Context 来创立一个 cancelCtx。WithCancel() 办法返回两个字段,一个是基于传入的 Context 生成的 cancelCtx,另一个是 CancelFunc。

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
    if parent == nil {panic("cannot create context from nil parent")
    }
    c := newCancelCtx(parent)
    propagateCancel(parent, &c)
    return &c, func() { c.cancel(true, Canceled) }
}

WithCancel 调用了两个内部办法:newCancelCtx、propagateCancel。newCancelCtx 比较简单,依据传入的 context,返回了一个 cancelCtx 构造体。

func newCancelCtx(parent Context) cancelCtx {return cancelCtx{Context: parent}
}

propagateCancel 从名字能够看出,就是将 cancel 流传。如果父 Context 反对勾销,那么咱们须要建设一个告诉机制,这样父节点勾销的时候,告诉子节点也勾销,层层流传。

在 propagateCancel 中,如果 父 Context 是 cancelCtx 类型且未勾销,会将 子 Context 挂在它上面,造成一个树结构;其余状况都不会挂载。

func propagateCancel(parent Context, child canceler) {
  
  // 如果 parent 不反对勾销,那么就不反对勾销流传,间接返回
    done := parent.Done()
    if done == nil {return}

  // 到这里阐明 done 不为 nil,parent 反对勾销
  
    select {
    case <-done:
        // 如果 parent 此时曾经勾销了,那么间接通知子节点也勾销
        child.cancel(false, parent.Err())
        return
    default:
    }

  // 到这里阐明此时 parent 还未勾销
  
  // 如果 parent 是未勾销的 cancelCtx 
    if p, ok := parentCancelCtx(parent); ok {
    
    // 加锁,避免并发更新
        p.mu.Lock()
    
    // 再次判断,因为有可能上一个取得锁的进行了勾销操作。// 如果 parent 曾经勾销了,那么子节点也间接勾销
        if p.err != nil {child.cancel(false, p.err)
        } else {
      // 把子 Context 挂到父节点 parent cancelCtx 的 children 字段下
      // 之后 parent cancelCtx 勾销时,能告诉到所有的 子 Context 
            if p.children == nil {p.children = make(map[canceler]struct{})
            }
            p.children[child] = struct{}{}
        }
        p.mu.Unlock()} else {
    
   // parent 不是 cancelCtx 类型,可能是用户本人实现的 Context
        atomic.AddInt32(&goroutines, +1)
    // 启动一个协程监听,如果 parent 勾销了,子 Context 也勾销
        go func() {
            select {case <-parent.Done():
                child.cancel(false, parent.Err())
            case <-child.Done():}
        }()}
}

cancel 办法就是来勾销 cancelCtx,次要的工作是:敞开 c.done 中的 channel,给 err 赋值,而后级联勾销所有 子 Context。如果 removeFromParent 为 true,会从父节点中删除以该节点为树顶的树。

cancel() 办法只负责本人管辖的范畴,即本人以及本人的子节点,而后依据配置判断是否须要从父节点中移除本人为顶点的树。如果子节点还有子节点,那么由子节点负责解决,不必本人负责了。

propagateCancel() 中有三处调用了 cancel() 办法,传入的 removeFromParent 都为 false,是因为过后基本没有挂载,不须要移除。而 WithCancel 返回的 CancelFunc,传入的 removeFromParent 为 true,是因为调用 propagateCancel 有可能产生挂载,当产生挂载时,调用 cancel() 就须要移除了。

func (c *cancelCtx) cancel(removeFromParent bool, err error) {// err 是指勾销的起因,必传,cancelCtx 中是 errors.New("context canceled")
    if err == nil {panic("context: internal error: missing cancel error")
    }
  
  // 波及到爱护字段值的批改,都须要加锁
    c.mu.Lock()
  
  // 如果该 Context 曾经勾销过了,间接返回。屡次调用 cancel,不会产生额定成果
    if c.err != nil {c.mu.Unlock()
        return 
    }
  
  // 给 err 赋值,这里 err 肯定不为 nil
    c.err = err
  
  // close channel
    d, _ := c.done.Load().(chan struct{})
  // 因为 c.done 是懒加载,有可能存在 nil 的状况
  // 如果 c.done 中没有值,间接赋值 closedchan;否则间接 close
    if d == nil {c.done.Store(closedchan)
    } else {close(d)
    }
  
  // 遍历以后 cancelCtx 所有的子 Context,让子节点也 cancel
  // 因为以后的 Context 会被动把子 Context 移除,子 Context 不必被动从 parent 中脱离
  // 因而 child.cancel 传入的 removeFromParent 为 false
    for child := range c.children {child.cancel(false, err)
    }
  // 将 children 置空,相当于移除本人的所有子 Context
    c.children = nil
    c.mu.Unlock()
    
  // 如果以后 cancelCtx 须要从下层的 cancelCtx 移除,调用 removeChild 办法
  // c.Context 就是本人的父 Context
    if removeFromParent {removeChild(c.Context, c)
    }
}

从 propagateCancel 办法中能够看到,只有 parent 属于 cancelCtx 类型,才会将本人挂载。因而 removeChild 会再次判断 parent 是否为 cancelCtx,和之前的逻辑保持一致。找到的话,再将本人移除,须要留神的是,移除会把本人及其本人上面的所有子节点都移除。

如果上一步 propagateCancel 办法将本人挂载到了 A 上,然而在调用 cancel() 时,A 曾经勾销过了,此时 parentCancelCtx() 会返回 false。不过这没有关系,A 勾销时曾经将挂载的子节点移除了,以后的子节点不必将本人从 A 中移除了。

func removeChild(parent Context, child canceler) {
  // parent 是否为未勾销的 cancelCtx
    p, ok := parentCancelCtx(parent)
    if !ok {return}
  // 获取 parent cancelCtx 的锁,批改爱护字段 children
    p.mu.Lock()
  // 将本人从 parent cancelCtx 的 children 中删除
    if p.children != nil {delete(p.children, child)
    }
    p.mu.Unlock()}

parentCancelCtx 判断 parent 是否为 未勾销的 cancelCtx。勾销与否容易判断,难判断的是 parent 是否为 cancelCtx,因为有可能其余构造体内嵌了 cancelCtx,比方 timerCtx,会通过比对 channel 来确定。

func parentCancelCtx(parent Context) (*cancelCtx, bool) {
  
  // 如果 parent context 的 done 为 nil,阐明不反对 cancel,那么就不可能是 cancelCtx
    // 如果 parent context 的 done 为 closedchan,阐明 parent context 曾经 cancel 了
    done := parent.Done()
    if done == closedchan || done == nil {return nil, false}
  
  // 到这里阐明反对勾销,且没有被勾销
  
    // 如果 parent context 属于原生的 *cancelCtx 或衍生类型,须要持续进行后续判断
    // 如果 parent context 无奈转换到 *cancelCtx,则认为非 cancelCtx,返回 nil,fasle
    p, ok := parent.Value(&cancelCtxKey).(*cancelCtx)
    if !ok {return nil, false}
 
  // 通过下面的判断后,阐明 parent context 能够被转换为 *cancelCtx,这时存在多种状况:
    //   - parent context 就是 *cancelCtx
    //   - parent context 是规范库中的 timerCtx
    //   - parent context 是个本人自定义包装的 cancelCtx
    //
    // 针对这 3 种状况须要进行判断,判断办法就是: 
    //   判断 parent context 通过 Done() 办法获取的 done channel 与 Value 查找到的 context 的 done channel 是否统一
    // 
    // 统一状况阐明 parent context 为 cancelCtx 或 timerCtx 或 自定义的 cancelCtx 且未重写 Done(),// 这种状况下能够认为拿到了底层的 *cancelCtx
    // 
    // 不统一状况阐明 parent context 是一个自定义的 cancelCtx 且重写了 Done() 办法,并且并未返回规范 *cancelCtx 的
    // 的 done channel,这种状况须要独自解决,故返回 nil, false
    pdone, _ := p.done.Load().(chan struct{})
    if pdone != done {return nil, false}
    return p, true
}

timerCtx

简介

timerCtx 嵌入了 cancelCtx,并新增了一个 timer 和 deadline 字段。timerCtx 的勾销能力是复用 cancelCtx 的,只是在这个根底上减少了定时勾销而已。

在咱们的应用过程中,有可能还没到 deadline,工作就提前完成了,此时须要手动调用 CancelFunc。

func slowOperationWithTimeout(ctx context.Context) (Result, error) {ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
    defer cancel()  // 如果未到截止工夫,slowOperation 就实现了,尽早调用 cancel() 开释资源
        return slowOperation(ctx)
}

类型定义

type timerCtx struct {
   cancelCtx // 内嵌 cancelCtx
   timer *time.Timer // 受 cancelCtx.mu 互斥锁的爱护

   deadline time.Time // 截止工夫
}

Deadline() 返回 deadline 字段的值

func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {return c.deadline, true}

WithDeadline

WithDeadline 基于 parent Context 和 工夫点 d,返回了一个定时勾销的 Context,以及一个 CancelFunc。返回的 Context 有三种状况被勾销:1. 达到了指定工夫,就会被动勾销;2. 手动调用了 CancelFunc;3. 父 Context 勾销,导致该 Context 被勾销。这三种状况哪种先到,就会首次触发勾销操作,后续的再次勾销不会产生任何成果。

如果传入 parent Context 的 deadline 比指定的工夫 d 还要早,此时 d 就没用处了,间接依赖 parent 勾销流传就能够了。

func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
  
  // 传入的 parent 不能为 nil
    if parent == nil {panic("cannot create context from nil parent")
    }
  
  // parent 也有 deadline,并且比 d 还要早,间接依赖 parent 的勾销流传即可
    if cur, ok := parent.Deadline(); ok && cur.Before(d) {
        // The current deadline is already sooner than the new one.
        return WithCancel(parent)
    }
  
  // 定义 timerCtx 接口
    c := &timerCtx{cancelCtx: newCancelCtx(parent),
        deadline:  d,
    }
  
  // 设置流传,如果 parent 属于 cancelCtx,会挂载到 children 字段上
    propagateCancel(parent, c)
  
  // 间隔截止工夫 d 还有多久
    dur := time.Until(d)
    if dur <= 0 {
    // 曾经到了截止工夫,间接勾销,同时从 parent 中勾销挂载
    // 因为是超时,勾销时的 err 是 DeadlineExceeded
        c.cancel(true, DeadlineExceeded) 
    
    // 再返回 c 和 CancelFunc,曾经勾销挂载了,此时的 CancelFunc 不会从 parent 中勾销挂载
    // 前面再次调用 CancelFunc 不会产生任何成果了
    // 被动勾销的话,err 是 Canceled
        return c, func() { c.cancel(false, Canceled) }
    }
  
  // 还没有到截止工夫,定义一个定时器,过了 dur 会主动勾销
    c.mu.Lock()
    defer c.mu.Unlock()
    if c.err == nil {c.timer = time.AfterFunc(dur, func() {
      // 因为是到了截止工夫才勾销,err 是 DeadlineExceeded
            c.cancel(true, DeadlineExceeded)
        })
    }
  
  // 返回 c 和 cancelFunc,被动勾销的 err 是 Canceled
    return c, func() { c.cancel(true, Canceled) }
}

接下来咱们看下 cancel 办法,timerCtx 的 cancel 办法 就是调用内嵌 cancelCtx 的 cancel() 办法,默认是不从父节点移除

func (c *timerCtx) cancel(removeFromParent bool, err error) {c.cancelCtx.cancel(false, err)
  
  // 从父节点中移除
    if removeFromParent {removeChild(c.cancelCtx.Context, c)
    }
  
  // 把定时器停了,开释资源
  // 有可能还没到 deadline,手动触发了 CancelFunc,此时把 timer 停了
    c.mu.Lock()
    if c.timer != nil {c.timer.Stop()
        c.timer = nil
    }
    c.mu.Unlock()}

WithTimeout

WithTimeout 就是基于 WithDeadline,deadline 就是基于以后工夫计算的

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

总结

本篇文章,咱们通过源码 + 示例的形式,一起学习了 context 包相干的构造以及实现逻辑,包含如下内容

Context 接口:定义了一些接口办法和标准

emptyCtx:空的 Context,Background() 和 TODO() 办法就是应用的 emptyCtx

valueCtx:用于保留键值对,查问时是递归查问,能够用于 LogID 这种全局 id 的保留

cancelCtx:能够勾销的 Context,用于勾销信号的传递

timerCtx:定时勾销的 cancelCtx

更多

集体博客: https://lifelmy.github.io/

微信公众号:漫漫 Coding 路

正文完
 0