什么是context包
关于context包的描述有一篇官方的博客,讲的比较详细,我也推荐过多次,这里结合博客内容再讨论下。 最早的context包出现于golang.org/x/net/context
,作为一个候补标准库存在,直到Go1.7版本的发布,context包被添加到标准库中, 同时在Go1.7版本中,标准库中的其它包也对应使用了context包来做一些并发控制的事情,例如net/http
。在即将发布的Go1.8版本中,在更多的标准库中引入了context
,例如:database/sql
。context
将会Go语言中编写并发模式代码的重要工具。
context
主要类型Context
的定义
// A Context carries a deadline, cancelation signal, and request-scoped values
// across API boundaries. Its methods are safe for simultaneous use by multiple
// goroutines.
type Context interface {
// Done returns a channel that is closed when this Context is canceled
// or times out.
Done() <-chan struct{}
// Err indicates why this context was canceled, after the Done channel
// is closed.
Err() error
// Deadline returns the time when this Context will be canceled, if any.
Deadline() (deadline time.Time, ok bool)
// Value returns the value associated with key or nil if none.
Value(key interface{}) interface{}
}
Done
方法返回一个通道,通过这个通道可以监听信号,判断当前的context
是否已经关闭。Err方法返回一个错误,描述context为何关闭。Deadline方法返回当前context的deadline以及是否被设置了deadline。Value方法用于获取context中包含的值。
从上面的逻辑可以看出,context不仅可以控制并发逻辑,而且本身也可以携带变量,类似于Map。并且提供Value方法用于获取指定Key的Value值。 上面描述一个context具备的功能,接下来描述下如何构造一个context。Context之间是具有父子关系的,新的Context往往从已有的Context中创建, 因此,最终所有的context组成一颗树状的结构。在context包中提供一个创建初始Context的方法
// Background returns an empty Context. It is never canceled, has no deadline,
// and has no values. Background is typically used in main, init, and tests,
// and as the top-level Context for incoming requests.
func Background() Context
可以认为Backgraund就是所有context树的根。
WithCancel
和WithTimeout
两个方法用于在已有的context
上创建新的context
,同时从新的context中可以获取到旧的context中保存的Key,Value。通常会在处理完一次请求之后,关闭当前context。
// WithCancel returns a copy of parent whose Done channel is closed as soon as
// parent.Done is closed or cancel is called.
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
// A CancelFunc cancels a Context.
type CancelFunc func()
// WithTimeout returns a copy of parent whose Done channel is closed as soon as
// parent.Done is closed, cancel is called, or timeout elapses. The new
// Context's Deadline is the sooner of now+timeout and the parent's deadline, if
// any. If the timer is still running, the cancel function releases its
// resources.
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
WithCancel 方法也通常使用在停掉某些并发调用的逻辑上,WithTimeout 方法用于控制向下游发起请求时的超时。基于这些方法创建的context的关闭只会影响其本身以及后代节点,而不会影响其祖先节点。
最后还有一个设置context中Key,Value的方法
// WithValue returns a copy of parent whose Value method returns val for key.
func WithValue(parent Context, key interface{}, val interface{}) Context
同样也是基于已有的ctx创建出一个新的context,后续可以通过context类型自带的方法Value来获取对应Key的值。
context可以被多个并发的Goroutine使用,对context的访问是并发安全的。
例子
上面简单描述了context的接口,形象的表达,多个具有父子关系的context就是一颗树,这颗树的根节点是Background。除了根context之外,所有的ctx都是基于已有context而创建,下面举一个简单的例子演示context的使用方法。
func Counter(ctx context.Context) int {
count := 0
for {
select {
case <-ctx.Done():
return count
default:
}
count++
time.Sleep(time.Second)
}
return count
}
func main() {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
c := make(chan int)
go func() {
c <- Counter(ctx)
}()
time.Sleep(5 * time.Second)
cancel()
fmt.Println(<-c)
}
输出结果: 5
我们修改main
函数如下:
func main() {
ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
fmt.Println(Counter(ctx))
}
输出结果:5
上面两个是比较简单地使用context控制并发的例子,WithCancel 和 WithTimeout 可以在不同场景下使用。
在实际开发中,context更多的是和网络请求结合使用,例如HTTP请求(Go1.7增加Request的WithContext方法),数据请求(Go1.8会加入Context控制超时),RPC调用请求(kite框架中的client.Call(ctx, ...))。完整的例子可以查看官方博客