Context包用作上下文管理,在API边界或者goroutine之间创建信息。主要用作数据传递,截止时间,取消信号等相关的操作。
Context是一个接口,它的定义如下:
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
Deadline定义了在什么时间点,该操作需要被取消。当没有定义deadline的时候,ok返回的是false。
Done返回一个chan,如果这个chan可以读取(由于parent close了这个chan导致),说明应该取消操作,释放资源。
Err返回的是操作取消的原因。
Value用来在goroutine间或API之间传递值。
Context接口还定义了这些函数:
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
根据函数的名字,一看就知道其作用。
废话不多说,下面我们通过例子来学习Context的使用。
package main
import (
"context"
"fmt"
"time"
)
type key int
const (
// NAME name key
NAME key = iota
)
func sayHello(ctx context.Context) {
name := fmt.Sprintf("%v", ctx.Value(NAME))
for {
select {
case <-ctx.Done():
fmt.Println("routine cancelled - reason:", ctx.Err())
return
case <-time.After(2 * time.Second):
fmt.Println("hello ", name)
}
}
}
func main() {
// 创建一个带Value的context
ctx := context.WithValue(context.Background(), NAME, "bob")
// 从刚创建的context派生出一个返回取消函数的context
ctx, cancel := context.WithCancel(ctx)
go sayHello(ctx)
time.Sleep(10 * time.Second)
// 调用取消函数,取消子routine
cancel()
time.Sleep(3 * time.Second)
fmt.Println("main routine exit")
}
上面的例子,我们首先创建了一个带Value的context,这里演示了如何在routine间通过context传递数据。然后在通过刚创建的context派生出一个WithCancel的context。通过返回的cancel函数,main routine可以随时取消子routine。我们这里的子routine会一直打印 hello bob ,直到10秒之后,main routine通知取消。子routine退出时,打印出了退出的原因。通过ctx.Err()函数。
将上面的代码保存为context_demo.go然后运行一下,结果如下:
$ go run context_demo.go
hello bob
hello bob
hello bob
hello bob
routine cancelled - reason: context canceled
main routine exit
是不是很简单。
我们下面演示一下WithTimeout的用法,稍微修改一下上面的例子:
package main
import (
"context"
"fmt"
"time"
)
type key int
const (
// NAME name key
NAME key = iota
)
func sayHello(ctx context.Context) {
name := fmt.Sprintf("%v", ctx.Value(NAME))
for {
select {
case <-ctx.Done():
fmt.Println("routine cancelled - reason:", ctx.Err())
return
case <-time.After(2 * time.Second):
fmt.Println("hello ", name)
}
}
}
func main() {
// 创建一个带Value的context
ctx := context.WithValue(context.Background(), NAME, "bob")
// 从刚创建的context派生出一个超时取消的context
ctx, _ = context.WithTimeout(ctx, 10*time.Second)
go sayHello(ctx)
time.Sleep(10 * time.Second)
time.Sleep(3 * time.Second)
fmt.Println("main routine exit")
}
注意,这个例子里面没有调用cancel函数,而是等待超时。将代码保存为context_demo1.go然后运行一下:
$ go run context_demo1.go
hello bob
hello bob
hello bob
hello bob
routine cancelled - reason: context deadline exceeded
main routine exit
可以看到上面退出时打印的reason已经变成了 context deadline exceeded 而不是之前的 context canceled 。
WithDeadline和超时的差不多,就不举例了。
是不是很简单,赶紧试试吧。