context学习笔记

2021-05-21  本文已影响0人  鸿雁长飞光不度

context是Go语言提供的用于控制协程的重要工具包,提供了许多开发中非常实用的功能,下面就对各种各样的context进行分析,并结合案例说明应用场景。

1. Context概述

Context其实只是一个接口,凡是实现这个接口的都可以称之为context。该接口包含四个方法,官方对每个方法的功能和职责都做了非常详细的介绍。

1.Deadline() (deadline time.Time, ok bool)

 //该函数返回代表当前context完成工作后应该被cancel的截止时间,
 //如果没有设置截止时间,ok会等于false,函数多次调用都会返回相同的结果
Deadline() (deadline time.Time, ok bool)

2.Done() <-chan struct{}

// 返回一个channel,当代表当前context完成工作后应该被cancel时,channel会被关闭。如果一个上下文
// 不能被cancel,Done就会返回nil,连续调用会返回相同结果。done函数返回的channel的关闭
// 可能会在cancel函数返回后异步执行。Done函数通常依赖select函数实现需求。
//这里可以看到更多关于Done函数cancel的案例: https://blog.golang.org/pipelines
    Done() <-chan struct{}

使用案例


//Stream函数调用DoSomething函数生成values,
//然后把这些values发给名称为out的channel,
//直到context返回一个错误或者ctx.Done的channel被关闭。
func Stream(ctx context.Context, out chan<- Value) error {
    for {
        v, err := DoSomething(ctx)
        if err != nil {
            return err
        }
        select {
        case <-ctx.Done():
            return ctx.Err()
        case out <- v:
        }
    }
}
  1. Err() error
//如果Done返回的channel没有被关闭,Err函数返回nil,
//如果关闭了,Err会返回一个非nil的error解释原因:
//如果context是cancel了,error就是Canceled
//如果是截止时间到了,error就是DeadlineExceeded
//在Err返回一个非nil的error后,连续调用会得到相同的结果。
Err() error

4.Value(key interface{}) interface{}

//Value函数返回一个和当前context关联的key的value,如果没有value和key关联返回nil
//连续调用Value方法如果key相同,返回结果也相同。
//context的value仅用于请求数据的传输流程和api边界,不是用来给函数传递可选字段的。
//一个key代表了在一个context的指定的value,对于希望存储values在context里面的函数,
//通常是声明一个全局变量做key,然后把这个key作为参数,调用context.WithValue()、
//context.Value(),分别实现存值和取值。key的类型可以是任意支持相等比较任意类型
//使用者的包定义的这些key应该是非导出类型从而避免key冲突。
//使用者的包定义一个context的key应该提供一个类型安全的获取函数来说访问存储的value
Value(key interface{}) interface{}

2.Context类型

通过上面接口职责说明里面可以看到,不是所有的context都需要完全实现上面的方法,为了实现接口而去实现不需要的方法就会很多余,go语言通过一个empty类型的context解决了这个问题,下面会具体分析。

  1. emptyCtx
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"
}

2.cancelCtx

  //初始化一个cancelCtx实例
  //如果父context也支持cancel,添加到父节点上。
 // 返回cancelCtx实例和cancel方法。
 func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
    c := newCancelCtx(parent)
    propagateCancel(parent, &c)
    return &c, func() { c.cancel(true, Canceled) }
 }

添加到父parent函数的实现

func propagateCancel(parent Context, child canceler) {
    done := parent.Done() //
    if done == nil {
        return //说明parent不支持cancel,直接返回吧
    }

    select {
    case <-done:
        // parent已经cancel了,当前也执行cancel,并传递父parent的err
        child.cancel(false, parent.Err())
        return
    default:
    }
        // 查找支持parent的父节点
    if p, ok := parentCancelCtx(parent); ok {
        p.mu.Lock()
        if p.err != nil {
            // parent has already been canceled
            child.cancel(false, p.err)
        } else {
            if p.children == nil {
                p.children = make(map[canceler]struct{})
            }
                        //如果父parent支持cancel
            p.children[child] = struct{}{}
        }
        p.mu.Unlock()
    } else { // 如果parent的的父节点都不支持cancel
        atomic.AddInt32(&goroutines, +1)// 当前包内的协程数量开启数+1
        go func() {
            select {
            case <-parent.Done(): // 等待parent节点结束
                child.cancel(false, parent.Err()) // 在把当前的结束。
            case <-child.Done():
            }
        }()
    }
}

parentCancelCtx函数实现。

// 返回父级的中最基础的*cancelCtx,通过调用parent.Value(&cancelCtxKey)
// 寻找最里面的*cancelCtx,然后检查parent.Done()是否和*cancelCtx匹配,
// 如果不匹配,说明*cancelCtx被一个自定义实现包装过了,提供了一个不同的done 
// channel,在这种情况下我们不应该绕过它。
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
    done := parent.Done()
    if done == closedchan || done == nil {
        return nil, false
    }
    p, ok := parent.Value(&cancelCtxKey).(*cancelCtx)
    if !ok {
        return nil, false
    }
    p.mu.Lock()
    ok = p.done == done
    p.mu.Unlock()
    if !ok {
        return nil, false
    }
    return p, true
}
//  这是一个可以被取消的context,当调用Cancel的时候,所有的实现cancler接口的子context也会调用cancel
type cancelCtx struct {
// 这里是一个接口,蕴含的形式定义的,通常我们可以用emptyCtx来往这里放,
// emptyCtx完整实现了context,所以凡是这样写的都可以按重写需要的接口方法,比如
// 这个就没有重写deadLine方法,但是依然时context,其他类型的context都采用了类似形式。
    Context  
    mu       sync.Mutex            // protects following fields
    done     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 (c *cancelCtx) Value(key interface{}) interface{} {
    if key == &cancelCtxKey { // 这个key专门返回自己
        return c
    }
    return c.Context.Value(key) // 其他的调用c.Context.Value()方法实现。
}

// c.done可能是nil,生命周期nil,chan,close
func (c *cancelCtx) Done() <-chan struct{} {
    c.mu.Lock()
    if c.done == nil {
        c.done = make(chan struct{})
    }
    d := c.done
    c.mu.Unlock()
    return d
}

// 返回错误原因
func (c *cancelCtx) Err() error {
    c.mu.Lock()
    err := c.err
    c.mu.Unlock()
    return err
}

cancelCtx还实现了canceler,这接口代表了可以cancel的context应该实现的方法。

type canceler interface {
        // 取消函数(是否从父节点移出,err是取消原因,不能为nil)
    cancel(removeFromParent bool, err error) // 
       // 完成以后发的消息。
    Done() <-chan struct{}
}

对cancel方法的实现

func (c *cancelCtx) cancel(removeFromParent bool, err error) {
    if err == nil {
        panic("context: internal error: missing cancel error")
    }
    c.mu.Lock()
    if c.err != nil {
        c.mu.Unlock()
        return // already canceled
    }
    c.err = err
    if c.done == nil {
        c.done = closedchan //全局变量
    } else {
        close(c.done)
    }
    for child := range c.children {
        // NOTE: acquiring the child's lock while holding parent's lock.
        child.cancel(false, err)
    }
    c.children = nil
    c.mu.Unlock()

    if removeFromParent {
        removeChild(c.Context, c)
    }
}

使用案例

    var ctx, cancel = context.WithCancel(context.Background())
    for i := 0; i < 2; i++ {
        go func(ctx context.Context, i int) {

            go func(ctx2 context.Context,i2 int) {
                for ; ;  {
                    select {
                    case <-ctx.Done():
                        err := ctx.Err()
                        fmt.Printf("i2_1 function done value %v err %s\n", i2,err.Error())
                        return
                    default:
                        fmt.Println("i2_1 doing")
                        //time.Sleep(5*time.Microsecond)
                    }

                }
            }(ctx,i)

            go func(ctx2 context.Context,i2 int) {
                for ; ;  {
                    select {
                    case <-ctx.Done():
                        err := ctx.Err()
                        fmt.Printf("i2_2 function done value %v err %s\n", i2,err.Error())
                        return
                    default:
                        fmt.Println("i2_2 doing")
                        //time.Sleep(5*time.Microsecond)
                    }
                }

            }(ctx,i)

        }(ctx,i)
    }
    time.Sleep(100*time.Microsecond) // 模拟逻辑执行
    cancel() // 取消以后,创建的协程会收到取消通知
    time.Sleep(1000*time.Microsecond) // 模拟后续执行流程

创建了多个协程,包括嵌套创建的。然后在协程里面执行逻辑,并监听ctx.Done的消息。通过调用cancel方法每个协程都收到ctx的cancel消息了。
输出结果如下

i2_2 doing
i2_2 doing
i2_1 doing
i2_2 doing
i2_2 function done value 1 err context canceled
i2_1 function done value 0 err context canceled
i2_2 function done value 0 err context canceled
i2_1 function done value 1 err context canceled
  1. timerCtx
type timerCtx struct {
    cancelCtx
    timer *time.Timer // Under cancelCtx.mu.

    deadline time.Time
}

//WithDeadline返回带有一个调整了不迟于d的截止日期的父context的拷贝,如果父context截止日期
// 已经早于d,等同于返回的是parent,返回的上下文里面的Done channel会在截止时间过期、
// 或者返回的cancel函数被手动执行,或者parent的context的Done channel被关闭时关闭。
// 不论哪一个情况第一次发生都会关闭。

// canceling一个context会释放和它关联的资源,所以代码应该在完成在context的操作后立即调用。

func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
    if cur, ok := parent.Deadline(); ok && cur.Before(d) {
        // 当前设置的时间比parent的早,用parent的
        return WithCancel(parent)
    }
    c := &timerCtx{
        cancelCtx: newCancelCtx(parent),
        deadline:  d,
    }
    propagateCancel(parent, c)
    dur := time.Until(d)
    if dur <= 0 {
              //过期
        c.cancel(true, DeadlineExceeded) // deadline has already passed
        return c, func() { c.cancel(false, Canceled) }
    }
    c.mu.Lock()
    defer c.mu.Unlock()
    if c.err == nil {
        c.timer = time.AfterFunc(dur, func() {
            c.cancel(true, DeadlineExceeded) //超时
        })
    }
        // 外面可以自己调用,主动调用的原因是【取消】
    return c, func() { c.cancel(true, Canceled) }
}

// WithTimeout 返回的结果是 WithDeadline(parent, time.Now().Add(timeout)).
//
// 取消context操作将会释放和它关联的资源, 所以代码里面应该尽可能早的在上线文中调用cancel:
//
//  func slowOperationWithTimeout(ctx context.Context) (Result, error) {
//      ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
//      defer cancel()  //如果slowOperation在超时之前完成释放资源。
//      return slowOperation(ctx)
//  }
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
    return WithDeadline(parent, time.Now().Add(timeout))
}

接口实现

// 直接返回截止时间
func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
    return c.deadline, true
}

// 和cancelCtx差不多,只是关闭了定时器。
func (c *timerCtx) cancel(removeFromParent bool, err error) {
    c.cancelCtx.cancel(false, err)
    if removeFromParent {
        // Remove this timerCtx from its parent cancelCtx's children.
        removeChild(c.cancelCtx.Context, c)
    }
    c.mu.Lock()
    if c.timer != nil {
        c.timer.Stop()
        c.timer = nil
    }
    c.mu.Unlock()
}

4.valueCtx

// WithValue returns a copy of parent in which the value associated with key is
// val.
// 设置值
func WithValue(parent Context, key, val interface{}) Context {
    if key == nil {
        panic("nil key")
    }
        // key是可以用来相等比较
    if !reflectlite.TypeOf(key).Comparable() {
        panic("key is not comparable")
    }
    return &valueCtx{parent, key, val}
}

func (c *valueCtx) Value(key interface{}) interface{} {
    if c.key == key {
        return c.val
    }
        // 会从父context去查询。
    return c.Context.Value(key)
}

使用案例,下面的案例来着官方

// user包定义了一个User类型存储在context。
package user

import "context"

// User 是存储在context里面的值。
type User struct {...}

// key定义了非导出的数据类型,仅包内可见,
// 这避免了和其他包的冲突
type key int

// userKey是代表在context里面的user.User作为value时对应的key,也是非导出的,
// 使用者应该使用 ser.NewContext和user.FromContext函数,而不是直接使用这个key
var userKey key
// 返回一个新的context并且设置一个value u.
func NewContext(ctx context.Context, u *User) context.Context {
    return context.WithValue(ctx, userKey, u)
}

// 获取存储在context里面的User类型的value。
func FromContext(ctx context.Context) (*User, bool) {
    u, ok := ctx.Value(userKey).(*User)
    return u, ok
}      

总结:

上一篇 下一篇

猜你喜欢

热点阅读