golang的context包详解 带源码

一、出现原因以及作用

在go语言并发编程中,用一个goroutine来处理一个任务,而它又可能再去创建多个goroutine来负责不同子任务。 这些场景中往往需要在API边界之间以及过程之间传递截止时间、取消信号或其他请求相关的数据,这时候就可以使用context!
在这里插入图片描述

二、Context主要内容

  1. Context接口
    type Context interface {
       Deadline() (deadline time.Time, ok bool)
       Done() <-chan struct{}
       Err() error
       Value(key any) any
    }
    
  2. Context的四种实现
    • emptyCtx:本质是一个int,简单的返回nil、false等,Background和TODO这两个函数内部都会创建emptyCtx。其中Background主要用于初始化时获得一个ctx根节点。TODO官方文档的解释是在本来应该使用外层传递的ctx,而外层却没有传递的地方使用。

      type emptyCtx int
      
      var (
         background = new(emptyCtx)
         todo       = new(emptyCtx)
      )
      
      func Background() Context {
         return background
      }
      
      func TODO() Context {
         return todo
      }
      
    • cancelCtx:这是一种可取消的Context,done用于获取Context的取消通知,children用于存储以当前节点为根节点的所有可取消的Context,以便在根节点取消时,可以把它的子节点一起取消,err用于存储取消时指定的错误信息,mu用来保证线程并发安全。WithCancel函数可以把一个Context包装为cancelCtx,并提供一个取消函数。调用它可以取消对应的Context。

      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
      }
      
      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) }
      }
      
    • timerCtx:在cancelCtx基础上封装了一个定时器和一个截止时间,这样既可以根据需要主动取消,也可以在到达deadline时,通过timer来触发取消动作,timer也会由cancelCtx结构体的mu来保护并发安全。WithTimeout函数和WithDeadline函数本质上都是调用了WithDeadline,不同是WithTimeout取了一个时间段,而WithDeadline是需要指定一个时间点。

      type timerCtx struct {
         cancelCtx
         timer *time.Timer // Under cancelCtx.mu.
      
         deadline time.Time
      }
      
      func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
         return WithDeadline(parent, time.Now().Add(timeout))
      }
      
      func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
         ...
         return c, func() { c.cancel(true, Canceled) }
      }
      
    • valueCtx:它用来支持键值对打包,WithValue函数可以给Context附加一个键值对信息,这样就可以通过context传递数据了。

      type valueCtx struct {
         Context
         key, val any
      }
      
      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}
      }
      

三、使用注意项

  1. cancelCtx的取消流程
    在这里插入图片描述

    • 如果ctx2先取消,只会影响以它为根节点的context
    • 如果ctx1先取消,就可以根据children中的记录,把ctx1子节点中可以取消的context全部cancel掉 (包括ctx2)
      由此可知,cancel流程是根据父子结点的顺序执行的
  2. valueCtx取值问题
    当我们使用的相同key值,keyB会覆盖keyA的value

    ctx := context.Background()
    	
    	var keyA = "keyA"
    	ctxA := context.WithValue(ctx, keyA, "A")
    	
    	var keyB = "keyA"
    	ctxb := context.WithValue(ctxA, keyB, "B")
    	
    	fmt.Println("keyA", ctxb.Value(keyA)) //B
    	fmt.Println("keyB", ctxb.Value(keyB)) //B
    
    • 其原因是,当前context中的key会比较是否等于要查找的key,如果不等于才会向父结点继续寻找。

      func (c *valueCtx) Value(key any) any {
         if c.key == key {
            return c.val
         }
         return value(c.Context, key)
      }
      
    • 为了解决这种key值相等的问题,可以将key类型设置为不相同解决

      type keyTypeA string
      type keyTypeB string
      
      var keyA keyTypeA = "keyA"
      var keyB keyTypeB = "keyB"
      
  3. Context本身是为了不可改变(immutable)的模式设计的,因此不要尝试修改ctx里保存的值

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值