很多框架中接口函数第一个参数统一是ctx context.Context接口,如net/http中conn.serve(ctx context.Context)方法,为什么要这么设计呢?
因为一般一个网络请求Request,会在多个Goroutine中处理,而这些Goroutine可能需要共享Request的一些信息;同时当Request被取消或者超时的时候,所有从这个Request创建的所有Goroutine也应该被结束。上下文则几乎已经成为传递与请求同生存周期变量的标准方法。
context用于Goroutine之间共享状态变量,另一个gorutine通过设置ctx变量值,传递过期或撤销信号给被调用的程序单元。
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
Goroutine的创建和调用关系总是像层层调用的,而更靠顶部的Goroutine应有办法主动关闭其下属的Goroutine。
Context结构也应该像一棵树,要创建Context树,第一步就是要得到根节点,context.Background函数的返回值就是根节点。
创建子context 函数 |
解释 |
---|---|
|
该Context一般由接收请求的第一个Goroutine创建,即根节点,一般返回emptyCtx |
|
创建子context,返回取消方法,父Goroutine可以调用取消子goroutine |
|
创建子context,到了dealine或者被父gorutine调用返回的取消方法,终止 |
|
创建子context,调用时间过了timeout或者被父gorutine调用返回的取消方法,终止 WithDeadline(parent, time.Now().Add(timeout)) |
|
返回valueCtx,传递了kv对到子context |
子节点需要类似如下代码来接收是否已结束,并退出该Goroutine:
select {
case <-cxt.Done():
// do some clean...
}
ctx := context.WithValue(baseCtx, ServerContextKey, srv) // 服务创建子context
for {
rw, err := l.Accept() // 接收请求
connCtx := ctx
c := srv.newConn(rw)
c.setState(c.rwc, StateNew) // before Serve can return
go c.serve(connCtx) // 子routine传入子ctx
}
context通过构建树型关系的Context,达到上一层Goroutine给下层Goroutine
父子传递控制信号&共享变量