内容简介:context包专门用来简化处理单个请求的多个goroutine之间与请求域的数据、取消信号、截止时间等相关操作。Deadline返回一个time.Time,是当前Context的应该结束的时间,ok表示是否有deadline。Done方法在Context被取消或超时时返回一个close的channel,close的channel可以作为广播通知,告诉给context相关的函数要停止当前工作然后返回。
context包专门用来简化处理单个请求的多个goroutine之间与请求域的数据、取消信号、截止时间等相关操作。
核心数据结构
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
Deadline返回一个time.Time,是当前Context的应该结束的时间,ok表示是否有deadline。
Done方法在Context被取消或超时时返回一个close的channel,close的channel可以作为广播通知,告诉给context相关的函数要停止当前工作然后返回。
Err方法返回context为什么被取消。
Value可以让Goroutine共享一些数据,当然获得数据是协程安全的。但使用这些数据的时候要注意同步,比如返回了一个map,而这个map的读写则要加锁。
提供cancel的context
canceler interface定义了提供cancel函数的context:
type canceler interface {
cancel(removeFromParent bool, err error)
Done() <-chan struct{}
}
其现成的实现有4个:
emptyCtx:空的Context,只实现了Context interface; cancelCtx:继承自Context并实现了cancelerinterface timerCtx:继承自cancelCtx,可以用来设置timeout; valueCtx:可以储存一对键值对;
继承Context
context包提供了一些函数,协助用户从现有的 Context 对象创建新的 Context 对象。这些Context对象形成一棵树:当一个 Context对象被取消时,继承自它的所有Context都会被取消。
Background是所有Context对象树的根,它不能被取消,它是一个emptyCtx的实例:
var (
background = new(emptyCtx)
)
func Background() Context {
return background
}
生成Context的主要方法
WithCancel
返回一个cancelCtx示例,并返回一个函数,可以在外层直接调用cancelCtx.cancel()来取消Context。
```
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
c := newCancelCtx(parent)
propagateCancel(parent, &c)
return &c, func() { c.cancel(true, Canceled) }
}
#### WithDeadline
返回一个cancelCtx示例,并返回一个函数,可以在外层直接调用cancelCtx.cancel()来取消Context。
```
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
c := newCancelCtx(parent)
propagateCancel(parent, &c)
return &c, func() { c.cancel(true, Canceled) }
}
WithDeadline
返回一个timerCtx示例,设置具体的deadline时间,到达 deadline的时候,后代goroutine退出。
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
if cur, ok := parent.Deadline(); ok && cur.Before(deadline) {
return WithCancel(parent)
}
c := &timerCtx{
cancelCtx: newCancelCtx(parent),
deadline: deadline,
}
propagateCancel(parent, c)
d := time.Until(deadline)
if d <= 0 {
c.cancel(true, DeadlineExceeded) // deadline has already passed
return c, func() { c.cancel(true, Canceled) }
}
c.mu.Lock()
defer c.mu.Unlock()
if c.err == nil {
c.timer = time.AfterFunc(d, func() {
c.cancel(true, DeadlineExceeded)
})
}
return c, func() { c.cancel(true, Canceled) }
}
WithTimeout
和WithDeadline一样返回一个timerCtx示例,实际上就是WithDeadline包了一层,直接传入时间的持续时间,结束后退出。
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
}
WithValue
WithValue对应valueCtx ,WithValue是在Context中设置一个 map,这个Context以及它的后代的goroutine都可以拿到map 里的值。
func WithValue(parent Context, key, val interface{}) Context {
if key == nil {
panic("nil key")
}
if !reflect.TypeOf(key).Comparable() {
panic("key is not comparable")
}
return &valueCtx{parent, key, val}
}
应用
Context的使用最多的地方就是在Golang的web开发中,在http包的Server中,每一个请求在都有一个对应的goroutine去处理。请求处理函数通常会启动额外的goroutine用来访问后端服务,比如数据库和RPC服务。用来处理一个请求的goroutine通常需要访问一些与请求特定的数据,比如终端用户的身份认证信息、验证相关的token、请求的截止时间。 当一个请求被取消或超时时,所有用来处理该请求的 goroutine都应该迅速退出,然后系统才能释放这些goroutine占用的资源。虽然我们不能从外部杀死某个goroutine,所以我就得让它自己结束,之前我们用channel+select的方式,来解决这个问题,但是有些场景实现起来比较麻烦,例如由一个请求衍生出的各个 goroutine之间需要满足一定的约束关系,以实现一些诸如有效期,中止goroutine树,传递请求全局变量之类的功能。
保存上下文
我们可以在上下文中保存任何的类型的数据,用于在整个请求的生命周期去传递使用。
func middleWare(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
ctx := context.WithValue(req.Context(),"key","value")
next.ServeHTTP(w, req.WithContext(ctx))
})
}
func handler(w http.ResponseWriter, req *http.Request) {
value := req.Context().Value("value").(string)
fmt.Fprintln(w, "value: ", value)
return
}
func main() {
http.Handle("/", middleWare(http.HandlerFunc(handler)))
http.ListenAndServe(":8080", nil)
}
超时控制
这里用一个timerCtx来控制一个函数的执行时间,如果超过了这个时间,就会被迫中断,这样就可以控制一些时间比较长的操作,例如io,RPC调用等等。
func longRunningCalculation(timeCost int)chan string{
result:=make(chan string)
go func (){
time.Sleep(time.Second*(time.Duration(timeCost)))
result<-"Done"
}()
return result
}
func jobWithTimeoutHandler(w http.ResponseWriter, r * http.Request){
ctx,cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
select{
case <-ctx.Done():
log.Println(ctx.Err())
return
case result:=<-longRunningCalculation(5):
io.WriteString(w,result)
}
return
}
func main() {
http.Handle("/", jobWithTimeoutHandler)
http.ListenAndServe(":8080", nil)
}
结尾
context包通过构建树型关系的Context,来达到上一层Goroutine能对传递给下一层Goroutine的控制。可以传递一些变量来共享,可以控制超时,还可以控制多个Goroutine的退出。
据说在Google,要求Golang程序员把Context作为第一个参数传递给入口请求和出口请求链路上的每一个函数。这样一方面保证了多个团队开发的Golang项目能够良好地协作,另一方面它是一种简单的超时和取消机制,保证了临界区数据在不同的Golang项目中顺利传递。
所以善于使用context,对于Golang的开发,特别是web开发,是大有裨益的。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The Zen of CSS Design
Dave Shea、Molly E. Holzschlag / Peachpit Press / 2005-2-27 / USD 44.99
Proving once and for all that standards-compliant design does not equal dull design, this inspiring tome uses examples from the landmark CSS Zen Garden site as the foundation for discussions on how to......一起来看看 《The Zen of CSS Design》 这本书的介绍吧!