什么是上下文
上下文通常被翻译成上下文,它是一个比较抽象的概念。 一般理解为程序单元的一种运行状态、现场,上下存在上下层传递,内容向下传递。 在Go语言中,程序单元是指Goroutine。
每个Goroutine在运行之前,必须知道程序的当前运行状态。 这些执行状态通常封装在Context变量中,并传递给要执行的Goroutine。 上下文已成为传递请求和生存周期变量的标准方法。 在网络编程中,当接收单个网络请求请求并处理请求时,我们可能需要打开不同的Goroutine来获取数据和逻辑处理。 也就是说,一个请求Request由多个Goroutine处理。 这些Goroutine可能需要共享请求信息; 同时,如果Request取消或超时,则从此Request创建的所有Goroutine也必须退出。
# #上下文包
Go设计人员正在考虑多个Goroutine共享数据以及多个Goroutine管理机制。
context包不仅可以实现在程序单元之间共享状态变量的方法,而且可以通过简单的方法在被调用的程序单元外部设置ctx变量的值,将过期或取消的信号传递到被调用的程序单元在网络编程中,如果a调用b的API,b再次调用c的API,a调用b取消,b也调用c,在a、b、c的API调用之间传递上下文,通过判断其状态
context包的中心是context界面,定义如下:
` ` ` go
类型上下文接口{
Deadline () ) deadline time.Time,ok bool ) )。
Done () )
Err () error
value (key接口{ } )接口{ }
}
``````
Deadline返回超时时间。 Goroutine获取超时时间后,例如可以对部分io操作设定超时时间。
Done方法在Context被取消或过期时返回通道。 也就是说,它是指示上下文是否关闭的信号。
Done通道关闭后,Err方法指示Context被拆除的原因。
Value可以让Goroutine共享一些数据。 当然,获取数据是协和的安全。 但是,在使用这些数据时要注意同步。 例如,返回一个map,并锁定该map的读写。
Context界面既没有设置值和过期日期的方法,也没有直接取消它本身的方法。 也就是说,上下文不能改变或取消自己。 那么,如何在上下文中传达更改的状态呢?
# #使用上下文
无论Goroutine如何,他们的创建和调用关系总是像人的世代那样像分层调用一样进行,更高一级的Goroutine必须想办法积极关闭下属Goroutine的执行(否则,程序为了实现这一关系,Context结构也必须像树一样,叶的节点必须总是从根节点派生出来。
要创建Context树,请先获取根节点。 context.Background函数的返回值是根节点。
` ` ` go
func背景() Context
``````
此函数返回空的上下文。 此上下文通常由接收请求的第一个Goroutine创建,是与输入请求对应的上下文根节点,不会被取消、没有值或过期。 它经常作为处理请求的顶级上下文而存在。
如果有根节点,该怎么做其他子节点,敏感芹菜呢? context包提供了几个用于创建他们的函数。
` ` ` go
funcwithcancel(parentcontext ) (ctx Context,cancel CancelFunc ) )。
funcwithdeadline(parentContext,deadline time.Time ) (context,CancelFunc ) )。
funcwithtimeout(parentContext,timeout time.Duration ) ) context,CancelFunc )。
funcwithvalue(parentContext,key interface{},val interface{} ) context
``````
所有函数都接收上下文类型的参数parent,并返回上下文类型的值。 这将创建分层不同的节点。 子节点从复制父节点得到,根据接收参数设定子节点的状态值,可以将子节点传递给下级的Goroutine。
返回上一个问题:我该如何在上下文中传达改变的状态? 使用上下文的Goroutine无法取消某个操作。 其实这也是合乎常识的。 因为这些Goroutine是由一个父Goroutine创建的。
而理应只有父Goroutine可以取消操作。在父Goroutine中可以通过WithCancel方法获得一个cancel方法,从而获得cancel的权利。第一个WithCancel函数,它是将父节点复制到子节点,并且还返回一个额外的CancelFunc函数类型变量,该函数类型的定义为:
```go
type CancelFunc func()
```
调用CancelFunc对象将撤销对应的Context对象,这就是主动撤销Context的方法。在父节点的Context所对应的环境中,通过WithCancel函数不仅可创建子节点的Context,同时也获得了该节点Context的控制权,一旦执行该函数,则该节点Context就结束了,则子节点需要类似如下代码来判断是否已结束,并退出该Goroutine:
```go
select {
case
// do some clean...
}
```
WithDeadline函数的作用也差不多,它返回的Context类型值同样是parent的副本,但其过期时间由deadline和parent的过期时间共同决定。当parent的过期时间早于传入的deadline时间时,返回的过期时间应与parent相同。父节点过期时,其所有的子敏感的芹菜必须同时关闭;反之,返回的父节点的过期时间则为deadline。
WithTimeout函数与WithDeadline类似,只不过它传入的是从现在开始Context剩余的生命时长。他们都同样也都返回了所创建的子Context的控制权,一个CancelFunc类型的函数变量。
当顶层的Request请求函数结束后,我们就可以cancel掉某个context,从而层层Goroutine根据判断cxt.Done()来结束。
WithValue函数,它返回parent的一个副本,调用该副本的Value(key)方法将得到val。这样我们不光将根节点原有的值保留了,还在子敏感的芹菜中加入了新的值,注意若存在Key相同,则会被覆盖。
## 小结
context包通过构建树型关系的Context,来达到上一层Goroutine能对传递给下一层Goroutine的控制。对于处理一个Request请求操作,需要采用context来层层控制Goroutine,以及传递一些变量来共享。
Context对象的生存周期一般仅为一个请求的处理周期。即针对一个请求创建一个Context变量(它为Context树结构的根);在请求处理结束后,撤销此ctx变量,释放资源。
每次创建一个Goroutine,要么将原有的Context传递给Goroutine,要么创建一个子Context并传递给Goroutine。
Context能灵活地存储不同类型、不同数目的值,并且使多个Goroutine安全地读写其中的值。
当通过父Context对象创建子Context对象时,可同时获得子Context的一个撤销函数,这样父Context对象的创建环境就获得了对子Context将要被传递到的Goroutine的撤销权。
## 使用原则
使用Context的程序包需要遵循如下的原则来满足接口的一致性以及便于静态分析。
1. 不要把Context存在一个结构体当中,显式地传入函数。Context变量需要作为第一个参数使用,一般命名为ctx;
2. 即使方法允许,也不要传入一个nil的Context,如果你不确定你要用什么Context的时候传一个context.TODO;
3. 使用context的Value相关方法只应该用于在程序和接口中传递的和请求相关的元数据,不要用它来传递一些可选的参数;
4. 同样的Context可以用来传递到不同的goroutine中,Context在多个goroutine中是安全的;
5. 在子Context被传递到的goroutine中,应该对该子Context的Done信道(channel)进行监控,一旦该信道被关闭(即上层运行环境撤销了本goroutine的执行),应主动终止对当前请求信息的处理,释放资源并返回。
有疑问加站长微信联系(非本文作者))