go 中的 Context

2021-10-18  本文已影响0人  wayyyy

Go 语言的context 是应用开发常用的并发控制技术,它与 waitGroup 最大的不同点是 context 对于派生的 goroutine 有更强的控制力,它可以控制多级的 goroutine。

context 接口
type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <- chan struct{}
    Err() error
    Value(key interface{}) interface{}
}

context 包提供了四个方法创建不同类型的context:

空 context

context 包中定义了一个空的 context,名为 emptyCtx,为什么会需要 emptyCtx ?
context 接口定义了上下文需要包含的功能属性,至于如何实现,完全是灵活的,但是试想,某种情况下,我只需要设置和获取key-value的功能怎么办呢? 或者我只需要控制功能不需要Value(xxx)功能?因为接口定义的功能项必须都要实现,才能转化为接口实例。

如此,,一个巧妙的设计产生了,定义一个空的祖对象,实现了空函数(看似实现却只是空函数)用来欺骗编译器和运行时, 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
}

var background = new(emptyCtx)
func Background() Context {
    return background
}
cancelCtx

源码包中定义了该类型:

type cancelCtx strutc{
    Context
    
    mu       sync.Mutex
    done     chan struct{}
    children map[canceler]struct{}
    err      error
}

cancelCtx 与 deadline 和 value 无关,所以只需要实现DoneErr 接口即可。

Done 接口的实现:

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
}

Err 接口实现:

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

cancel 接口实现:

func (c *cancelCtx) cancel(removeFormParent bool, err error) {
    c.mu.Lock()
    
    c.err = err
    close(c.done)

    for child := range c.children {
        child.cancel(false, err)
    }
    c.children = nil
    c.mu.Unlock()
    
    if removeFromParent {
        removeChild(c.Context, c)
    }
}

cancelCtx 通过 withCancel() 创建cancelCtx 实例

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

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
    c := newCancelCtx(parent)
    propagateCancel(parent, &c)
    return &c, func() {  c.cancel(true, Canceled)  }
}

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

使用示例:

func HandelRequest(ctx context.Context) {
    go WriteRedis(ctx)
    go WriteDatabase(ctx)

    for {
        select {
        case <-ctx.Done():
            fmt.Println("HandleRequest Done.")
            return
        default:
            fmt.Println("HandleRequest running")
            time.Sleep(2 * time.Second)
        }
    }
}

func WriteRedis(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            fmt.Println("WriteRedis Done.")
            return
        default:
            fmt.Println("WriteRedis running")
            time.Sleep(2 * time.Second)
        }
    }
}

func WriteDatabase(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            fmt.Println("WriteDatabase Done.")
            return
        default:
            fmt.Println("WriteDatabase running")
            time.Sleep(2 * time.Second)
        }
    }
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    go HandelRequest(ctx)

    time.Sleep(time.Second * 5)
    fmt.Println("It's time to stop all sub goroutines!")
    cancel()

    time.Sleep(5 * time.Second)
}

HandleRequest 用于处理某个请求,其又会创建2个协程做不同的事,main 协程创建 context,并把context 在各个协程之间传递,main 协程在适当的时机可以"cancel"掉所有子协程。

timerCtx
type timerCtx struct {
    cancelCtx
    timer       *time.Timer
    deadline    time.Time
}

Deadline 接口实现:

cancel 接口实现:

可以通过 WithDeadline 来创建:

从上面可以看出:timerCtx 类型的 context 不仅支持手动cancel,也会在定时器到来后自动cancel。
也可以通过 WithTimeout 来创建。

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

valueCtx 只是在 Context 的基础上增加了一个 key-value 对,用于在各级协程之间传递数据。所以它只实现了 Value() 接口

func (c *valueCtx) Value(key interface{}) interface{} {
    if c.key == key {
        return c.val
    }
    return c.Context.Value(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}
}

示例:

func HandleRequest(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            fmt.Println("HandleRequest done")
            return
        default:
            fmt.Println("HandleRequest running, param: ", ctx.Value("parameter"))
            time.Sleep(2 * time.Second)
        }
    }
}

func main() {
    ctx, cancel := context.WithCancel(context.WithValue(context.Background(), "parameter", "1"))
    go HandleRequest(ctx)

    time.Sleep(time.Second * 10)
    fmt.Println("It's time to stop all sub goroutines!")
    cancel()

    time.Sleep(time.Second * 5)
}
小结

Context 仅仅是一个接口定义,根据实现不同,可以衍生出不同类型的context

几种context 实例可以互为父节点,从而组合成不同的应用形式。

上一篇下一篇

猜你喜欢

热点阅读