最近看到将go里面context的文章,之前对这块了解不多。感觉很奇怪为何parent context的取消同时可以影响到child context的取消,以此做链式控制。跟着网上前人的一些技术博客看了下context源码,代码不多也很容易看懂。这里记录下看到的几个有意思的地方。

第一个是WithCancel这个函数返回的时候:

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

最后返回了一个func(){c.cancel(true, Canceled)},这里很巧妙。用一个wrapper方法将实际函数内容返回出去,等到最终调用的时候才执行c.cancel(true, Canceled),跟python的装饰器一样。

同样在找到的Go语言并发模型:像Unix Pipe那样使用channel这篇博客里,使用channel实现的’pipline’也很有趣。摘抄一点代码

func gen(nums ...int) <-chan int {
    out := make(chan int)
    go func() {
        for _, n := range nums {
            out <- n
        }
        close(out)
    }()
    return out
}

这里将channel返回出来,然后协程里起始在往这个channel里写数据,但是由于channel没有被读取而阻塞。所以当channel被读取的时候,协程里往channel里放数据的操作才会执行。

说回context接口。除了最原始的backgroundtodo这两个emptyCtx类型之外,其余实现context接口的都是以cancelCtx作为基础。所以每个类型都会存在donechildren,parent context之所以可以影响到child context就是因为它在children属性里记录了所有child context;控制通过done这个cahnnel来实现,在协程中通过调用Done()函数监听到done这个channel,然后在cancel函数被调用的时候关闭掉donechannel,从而将一个信号让所有监听的地方收到;同时还会遍历当前context的’children’,去cancel掉所有的child context,致使所有监听child context的donechannel的地方收到信号。