go context
http包的Server中每个请求对应一个goroutine处理
上层任务取消后,所有的下层任务都会被取消;
中间某一层的任务取消后,只会将当前任务的下层任务取消,而不会影响上层的任务以及同级任务。
一个请求被取消或超市,所有用来处理该请求的goroutine都应退出
context包简化单个请求的多个goroutine之间的相关操作
context 接口
type Context interface {
Deadline() (deadline time.Time, ok bool) //返回任务被取消的截止时间
Done() <-chan struct{} //任务取消时返回一个关闭的channel
Err() error //channel关闭的原因
Value(key interface{}) interface{} //存储键值对(树)
}
emptyCtx
type emptyCtx int
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)
func Background() Context {
return background
}
func TODO() Context {
return todo
}
一般不会直接使用emptyCtx,而是使用由emptyCtx实例化的两个变量,分别可以通过调用Background和TODO方法得到。
Background通常被用于主函数、初始化以及测试中,作为一个顶层的context,也就是说一般我们创建的context都是基于Background;而TODO是在不确定使用什么context的时候才会使用。
几种context类型
valueCtx
WithValue(parent Context, key, val interface{}) Context 方法添加key-value
cancelCtx
WithCancel(parent Context) (ctx Context, cancel CancelFunc)方法创建可取消的context
timerCtx
WithDeadline(parent Context, d time.Time) (Context, CancelFunc)方法返回一个可取消的context(时间点)
WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) (相对当前时间的时间段)
net/http包源码中的应用
- server 开启服务时:
由ctx.Background()创建一个baseCtx;
并由context.WithValue()创建一个valueCtx存储server信息,建立一个连接,即开启一个协程携带ctx - 处理方法:
由context.WithValue(ctx)创建一个valueCtx存储本地地址信息;
创建一个cancelCtx,每读取一个request传入该ctx;(一旦请求超时,即可中断当前请求) - 读取request:
基于传入的ctx创建新的cancelCtx,设置到req上;
???生成的response中的cancelCtx保存了当前context取消方法。
(在处理构建response过程中如果发生错误,可直接调用response对象的cancelCtx方法结束当前请求;
在处理构建response完成之后,调用response对象的cancelCtx方法结束当前请求。)
注意事项
- 上游任务仅仅使用context通知下游任务不再需要,但不会直接干涉和中断下游任务的执行,由下游任务自行决定后续的处理操作,也就是说context的取消操作是无侵入的;
- context是线程安全的,因为context本身是不可变的(immutable),因此可以放心地在多个协程中传递使用
server程序
func handleSearch(w http.ResponseWriter, req *http.Request) { //注册到路由
// ctx is the `Context` for this handler. Calling cancel closes the
// ctx.Done channel, which is the cancellation signal for requests
// started by this handler.
var (
ctx context.Context
cancel context.CancelFunc
)
timeout, err := time.ParseDuration(req.FormValue("timeout"))
if err == nil {
// The request has a timeout, so create a `Context` that is
// canceled automatically when the timeout expires.
ctx, cancel = context.WithTimeout(context.Background(), timeout)
} else {
ctx, cancel = context.WithCancel(context.Background())
}
defer cancel() // Cancel ctx as soon as handleSearch returns.
// Check the search query.
query := req.FormValue("q")
if query == "" {
http.Error(w, "no query", http.StatusBadRequest)
return
}
// Store the user IP in ctx for use by code in other packages.
userIP, err := userip.FromRequest(req) //从一个 http.Request 对象中解析出 userIP
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
ctx = userip.NewContext(ctx, userIP) //新context,关联IP
// Run the Google search and print the results.
start := time.Now()
results, err := google.Search(ctx, query) //发送HTTP请求
elapsed := time.Since(start)
if err := resultsTemplate.Execute(w, struct {
Results google.Results
Timeout, Elapsed time.Duration
}{
Results: results,
Timeout: timeout,
Elapsed: elapsed,
}); err != nil {
log.Print(err)
return
}
userip包 :解析Client IP/将其关联到一个Context对象
func FromRequest(req *http.Request) (net.IP, error) //从一个 http.Request 对象中解析出 userIP
func NewContext(ctx context.Context, userIP net.IP) //ip关联到一个新的Context对象
func FromContext(ctx context.Context) (net.IP, bool) //从一个 Context 对象中解析 userIP
google包 : 发送HTTP请求
func Search(ctx context.Context, query string) (Results, error) // 发送一个http请求,并解析返回的JSON数据。
使用httpDo(ctx, req, func(resp *http.Response, err error)发送HTTP请求,在ctx.Done关闭时取消请求。