使用Context包

Context 简介

Context 是一个接口,它有四个方法:

d := time.Now().Add(50 * time.Millisecond)
ctx, cancel := context.WithDeadline(context.Background(), d)
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)

从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.

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()