【GO】context实现原理

0.实战场景题

使用golang实现一款自带过期自动释放的单机锁
(1)过期自动释放
(2)解锁身份校验
(3) context+goroutinue

1.前言

Golang context是Golang应用开发常用的并发控制技术,它与WaitGroup最大的不同点是context对于派生 goroutine有更强的控制力,它可以控制多级的goroutine。 context翻译成中文是”上下文”,即它可以控制一组呈树状结构的goroutine,每个goroutine拥有相同的上下文。
context实际上只定义了接口,凡是实现该接口的类都可称为是一种context,官方包中实现了几个常用的 context,分别可用于不同的场景。
在这里插入图片描述

2.数据结构

1.接口

type Context interface {
    Deadline() (deadline time.Time, ok bool)//返会ctx的过期时间
    Done() <-chan struct{}//返回context中的chan
    Err() error
    Value(key any) any//存放
}
  • deadline:返回ctx的过期时间
  • done:返回标识ctx是否结束的chan
  • value:返回存放对应的key的value,如果value值符合协议的标识,返回context自身

2.empty_context

type emptyCtx struct{}

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 any) any {
    return nil
}

3.context.background和context.todo

Background 返回一个非 nil、空的 [Context]。它永远不会被取消,没有值,也没有截止日期。它通常由 main 函数、初始化和测试使用,并用作传入请求的顶级 Context。
type backgroundCtx struct{ emptyCtx }
TODO 返回一个非 nil、空的 [Context]。代码应使用上下文。当不清楚使用哪个 Context 或尚不可用时,请执行 TODO(因为周围函数尚未扩展以接受 Context 参数)。

type todoCtx struct{ emptyCtx }
  1. context.Background():
  • 这是一个根上下文,没有截止日期,没有值,也没有被取消的能力。
  • 通常用作最顶层的 context,作为其他 context 的父节点。
  1. context.TODO():
  • 这个函数返回一个未经初始化的 context。
  • 当你不确定应该使用哪种 context 时,可以暂时使用这个。
    context包提供了4个方法创建不同类型的context,使用这四个方法时如果没有父context,都需要传入backgroud,即backgroud作为其父节点:
  • WithCancel()
  • WithDeadline()
  • WithTimeout()
  • WithValue()
    context包中实现Context接口的struct,除了emptyCtx外,还有cancelCtx、timerCtx和valueCtx三种,正是基于这三种context实例,实现了上述4种类型的context。

3.三种context类型

我们经常会使用到context.WithCancel()、context.WithDeadline()、context.WithTimeout()、context.WithValue()
;现在基于源码认识一下这三种Ctx,以便能学习其特性。

1.cancelCtx

type cancelCtx struct {
    Context

    mu       sync.Mutex            // protects following fields
    done     atomic.Value//懒加载  
    children map[canceler]struct{}//set记录当前节点的孩子
    err      error//ctx关闭后不为空                 
    cause    error//ctx关闭后不为空      
    }
type canceler interface {
    cancel(removeFromParent bool, err, cause error)
    Done() <-chan struct{}
}         

此context被cancle时会把其中的所有child都cancle掉。cancelCtx与deadline和value无关,所以只需要实现Done()和Err()接口外露接口即可。

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{})
}
func (c *cancelCtx) Err() error {
    c.mu.Lock()
    err := c.err
    c.mu.Unlock()
    return err
}

构造

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
    c := withCancel(parent)
    return c, func() { c.cancel(true, Canceled, nil) }//关闭管道的函数
}
安排在父级取消时取消子级。
它设置cancelCtx的父上下文。
func (c *cancelCtx) propagateCancel(parent Context, child canceler) {
    c.Context = parent

    done := parent.Done()
    if done == nil {
       return // parent is never canceled
    }

    select {
    case <-done:
       // parent is already canceled
       child.cancel(false, parent.Err(), Cause(parent))
       return
    default:
    }
检查父ctx是不是canCtx,如果是,需要把孩子节点添加到parrent的孩子set中
    if p, ok := parentCancelCtx(parent); ok {
       // parent is a *cancelCtx, or derives from one.
       p.mu.Lock()
       if p.err != nil {
          // parent has already been canceled
          child.cancel(false, p.err, p.cause)
       } else {
          if p.children == nil {
             p.children = make(map[canceler]struct{})
          }
          p.children[child] = struct{}{}
       }
       p.mu.Unlock()
       return
    }

    if a, ok := parent.(afterFuncer); ok {
       // parent implements an AfterFunc method.
       c.mu.Lock()
       stop := a.AfterFunc(func() {
          child.cancel(false, parent.Err(), Cause(parent))
       })
       c.Context = stopCtx{
          Context: parent,
          stop:    stop,
       }
       c.mu.Unlock()
       return
    }

    goroutines.Add(1)
    启动子协程等待父亲结束
    go func() {
       select {
       case <-parent.Done():
          child.cancel(false, parent.Err(), Cause(parent))
       case <-child.Done():
       }
    }()
}

2.timeCtx

type timerCtx struct {
    cancelCtx
    timer *time.Timer // Under cancelCtx.mu.

    deadline time.Time
}

timerCtx在cancelCtx基础上增加了deadline用于标示自动cancel的最终时间,而timer就是一个触发自动
cancel的定时器。

由此,衍生出WithDeadline()和WithTimeout()。实现上这两种类型实现原理一样,只不过使用语境不一样:

  • deadline: 指定最后期限,比如context将2018.10.20 00:00:00之时自动结束
  • timeout: 指定最长存活时间,比如context将在30s后结束。
    对于接口来说,timerCtx在cancelCtx基础上还需要实现Deadline()和cancel()方法,其中cancel()方法是重写的。

Deadline()方法仅仅是返回timerCtx.deadline而已。而timerCtx.deadline是WithDeadline()或
WithTimeout()方法设置的。
cancel()方法基本继承cancelCtx,只需要额外把timer关闭。
timerCtx被关闭后,timerCtx.cancelCtx.err将会存储关闭原因:
- 如果deadline到来之前手动关闭,则关闭原因与cancelCtx显示一致;
- 如果deadline到来时自动关闭,则原因为:”context deadline exceeded”

func WithDeadlineCause(parent Context, d time.Time, cause error) (Context, CancelFunc) {
    if parent == nil {
       panic("cannot create context from nil parent")
    }
    if cur, ok := parent.Deadline(); ok && cur.Before(d) {
       // The current deadline is already sooner than the new one.
       return WithCancel(parent)
    }
    c := &timerCtx{
       deadline: d,
    }
    c.cancelCtx.propagateCancel(parent, c)
    dur := time.Until(d)
    if dur <= 0 {
       c.cancel(true, DeadlineExceeded, cause) // deadline has already passed
       return c, func() { c.cancel(false, Canceled, nil) }
    }
    c.mu.Lock()
    defer c.mu.Unlock()
    if c.err == nil {
    //定时触发关闭
       c.timer = time.AfterFunc(dur, func() {
          c.cancel(true, DeadlineExceeded, cause)
       })
    }
    return c, func() { c.cancel(true, Canceled, nil) }
}

3.valueCtx

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

查找value,直到根节点或者找到返回

func value(c Context, key any) any {
    for {
       switch ctx := c.(type) {
       case *valueCtx:
          if key == ctx.key {
             return ctx.val
          }
          c = ctx.Context
       case *cancelCtx:
          if key == &cancelCtxKey {
             return c
          }
          c = ctx.Context
       case withoutCancelCtx:
          if key == &cancelCtxKey {
             // This implements Cause(ctx) == nil
             // when ctx is created using WithoutCancel.
             return nil
          }
          c = ctx.c
       case *timerCtx:
          if key == &cancelCtxKey {
             return &ctx.cancelCtx
          }
          c = ctx.Context
       case backgroundCtx, todoCtx:
          return nil
       default:
          return c.Value(key)
       }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值