设为首页 加入收藏

TOP

深度解密Go语言之context(二)
2019-06-12 10:13:59 】 浏览:354
Tags:深度 解密 语言 context
出来任何东西。也正是利用了这一点,子协程从 channel 里读出了值(零值)后,就可以做一些收尾工作,尽快退出。

Err() 返回一个错误,表示 channel 被关闭的原因。例如是被取消,还是超时。

Deadline() 返回 context 的截止时间,通过此时间,函数就可以决定是否进行接下来的操作,如果时间太短,就可以不往下做了,否则浪费系统资源。当然,也可以用这个 deadline 来设置一个 I/O 操作的超时时间。

Value() 获取之前设置的 key 对应的 value。

canceler

再来看另外一个接口:

type canceler interface {
    cancel(removeFromParent bool, err error)
    Done() <-chan struct{}
}

实现了上面定义的两个方法的 Context,就表明该 Context 是可取消的。源码中有两个类型实现了 canceler 接口:*cancelCtx*timerCtx。注意是加了 * 号的,是这两个结构体的指针实现了 canceler 接口。

Context 接口设计成这个样子的原因:

  • “取消”操作应该是建议性,而非强制性

caller 不应该去关心、干涉 callee 的情况,决定如何以及何时 return 是 callee 的责任。caller 只需发送“取消”信息,callee 根据收到的信息来做进一步的决策,因此接口并没有定义 cancel 方法。

  • “取消”操作应该可传递

“取消”某个函数时,和它相关联的其他函数也应该“取消”。因此,Done() 方法返回一个只读的 channel,所有相关函数监听此 channel。一旦 channel 关闭,通过 channel 的“广播机制”,所有监听者都能收到。

结构体

emptyCtx

源码中定义了 Context 接口后,并且给出了一个实现:

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
}

看这段源码,非常 happy。因为每个函数都实现的异常简单,要么是直接返回,要么是返回 nil。

所以,这实际上是一个空的 context,永远不会被 cancel,没有存储值,也没有 deadline。

它被包装成:

var (
    background = new(emptyCtx)
    todo       = new(emptyCtx)
)

通过下面两个导出的函数(首字母大写)对外公开:

func Background() Context {
    return background
}

func TODO() Context {
    return todo
}

background 通常用在 main 函数中,作为所有 context 的根节点。

todo 通常用在并不知道传递什么 context的情形。例如,调用一个需要传递 context 参数的函数,你手头并没有其他 context 可以传递,这时就可以传递 todo。这常常发生在重构进行中,给一些函数添加了一个 Context 参数,但不知道要传什么,就用 todo “占个位子”,最终要换成其他 context。

cancelCtx

再来看一个重要的 context:

type cancelCtx struct {
    Context

    // 保护之后的字段
    mu       sync.Mutex
    done     chan struct{}
    children map[canceler]struct{}
    err      error
}

这是一个可以取消的 Context,实现了 canceler 接口。它直接将接口 Context 作为它的一个匿名字段,这样,它就可以被看成一个 Context。

先来看 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
}

c.done 是“懒汉式”创建,只有调用了 Done() 方法的时候才会被创建。再次说明,函数返回的是一个只读的 channel,而且没有地方向这个 channel 里面写数据。所以,直接调用读这个 channel,协程会被 block 住。一般通过搭配 select 来使用。一旦关闭,就会立即读出零值。

Err()String() 方法比较简单,不多说。推荐看源码,非常简单。

接下来,我们重点关注 cancel() 方法的实现:

func (c *cancelCtx) cancel(removeFromParent bool, err error) {
    // 必须要传 err
    if err == nil {
        panic("context: internal error: missing cancel error")
    }
    c.mu.Lock()
    if c.err != nil {
        c.mu.Unlock()
        return // 已经被其他协程取消
    }
    // 给 err 字段赋值
    c.err = err
    // 关闭 channel,通知其他协程
    if c.done == nil {
        c.done = closedchan
    } else {
        close(c.done)
    }
    
    // 遍历它的所有子节点
    for child := range c.children {
        // 递归地取消所有子节点
        child.cancel(false, err)
    }
    // 将子节点置空
    c.children = nil
    c.mu.Unlock()

    if removeFromParent {
        // 从父节点中移除自己 
        removeChild(c.Context, c)
    }
}

总体来看,cancel() 方法的功能就是关闭 channel:c.done;递归地取消它的所有子节点;从父节点从删除自己。达到的效果是通过关闭 channel,将取消信号传递给了它的所有子节点。goroutine 接收到取消信号的方式就是 select 语句中的读 c.done 被选中。

我们再来看创建一个可取消的 Context 的方法:

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}
}

这是一个暴露给用户的方法,传入一个父 Context(这通常是一个 background,作为根节点),返回新建的 context,新 context 的 done channel 是新建的(前文讲过)。

当 WithCanc

首页 上一页 1 2 3 4 5 6 7 下一页 尾页 2/7/7
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇PHP转Go系列:数组与切片 下一篇Gin上传文件到MongoDB gridfs

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目