使用Context包
21 Oct 2017Context 简介
Context 是一个接口,它有四个方法:
- Deadline() 当Context 是设置了超时时间的Context, Deadline 返回超时时间。如果没有设置超时时间,ok 为 false
d := time.Now().Add(50 * time.Millisecond)
ctx, cancel := context.WithDeadline(context.Background(), d)
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
- Done() Done 返回的 Channel 会在以下情况被关闭
- 当 Context 的 Cancel 函数被调用后
- 当 Context 的 超时时间消耗完
- 当 Context 的 deadline 到期
- 该请求已完成,ServerHTTP()已经返回
从net/http/request.go:313 的注释中说明了Context 被取消掉的几种情况
// For incoming server requests, the context is canceled when the
// client's connection closes, the request is canceled (with HTTP/2),
// or when the ServeHTTP method returns.
-
Err() 当 Context 的 Cancel 函数被调用后, Err 返回 Canceled, 当超时时,返回 deadlineExceededError
-
Value() 当 key 存在时,返回对应 key 的 value, 否则返回 nil. 使用
ctx := context.WithValue(context.Background(), k, "Go")
Context package 部分源码
var Canceled = errors.New("context canceled")
type deadlineExceededError struct{}
func (deadlineExceededError) Error() string { return "context deadline exceeded" }
func (deadlineExceededError) Timeout() bool { return true }
func (deadlineExceededError) Temporary() bool { return true }
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
var (
background = new(emptyCtx)
to = new(emptyCtx)
)
func BackGround() Context {
return background
}
type CacelFunc func()
// 返回一个子 Context 和 cancel 函数
//当返回的 cancel 被调用时,Context.Done() 返回的 chan 将被关闭,它的子 context 的 cancel 也会被调用
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
c := newCancelCtx(parent)
propagateCancel(parent, &c)
return &c, func() { c.cancel(true, Canceled) }
}
作用
防止 goroutine 泄漏
// gen generates integers in a separate goroutine and
// sends them to the returned channel.
// The callers of gen need to cancel the context once
// they are done consuming generated integers not to leak
// the internal goroutine started by gen.
gen := func(ctx context.Context) <-chan int {
dst := make(chan int)
n := 1
go func() {
for {
select {
case <-ctx.Done():
return // returning not to leak the goroutine
case dst <- n:
n++
}
}
}()
return dst
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // cancel when we are finished consuming integers
for n := range gen(ctx) {
fmt.Println(n)
if n == 5 {
break
}
}
HTTP client 取消请求
// 创建一个可以取消的 Context
ctx, cancel := context.WithCancel(context.Background())
req, err := http.NewRequest("POST", "http://localhost", nil)
// 将 Context 保存到 新的 Request, 并返回它的指针,所以必须赋值给 req
req = req.WithContext(ctx)
// 执行请求
client := &http.Client{}
client.Do(req)
// 取消请求
cancel()