设为首页 加入收藏

TOP

Go语言 context包源码学习(六)
2023-07-23 13:25:51 】 浏览:113
Tags:语言 context 包源码
() 方法获取的 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:用于保存键值对,查询时是递归查询,可以用

首页 上一页 3 4 5 6 下一页 尾页 6/6/6
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇为什么说 Go 语言字符串是不可变.. 下一篇[grpc快速入门] 一 grpc生成与调用

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目