Go语言并发模型:使用 context
简介在 Go http包的Server中,每一个请求在都有一个对应的 goroutine 去处理。请求处理函数通常会启动额外的 goroutine 用来访问后端服务,比如数据库和RPC服务。用来处理一个请求的 goroutine 通常需要访问一些与请求特定的数据,比如终端用户的身份认证信息、验证相关的token、请求的截止时间。 当一个请求被取消或超时时,所有用来处理该请求的 goroutine 都应该迅速退出,然后系统才能释放这些 goroutine 占用的资源。 在Google 内部,我们开发了 阅读建议本文内容涉及到了 done channel,如果你不了解这个概念,那么请先阅读 "Go语言并发模型:像Unix Pipe那样使用channel"。 由于访问 package contextcontext 包的核心是 struct 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{} } 注意: 这里我们对描述进行了简化,更详细的描述查看 godoc:context
一个
继承 contextcontext 包提供了一些函数,协助用户从现有的
// 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
当请求处理函数返回时,与该请求关联的 // 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)
// WithValue returns a copy of parent whose Value method returns val for key. func WithValue(parent Context,key interface{},val interface{}) Context 当然,想要知道 一个栗子:Google Web Search我们的例子是一个 HTTP 服务,它能够将类似于 这个例子的代码存放在三个包里:
深入 server 程序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. 处理函数 (handleSearch) 将query 参数从请求中解析出来,然后通过 userip 包将client IP解析出来。这里 Client IP 在后端发送请求时要用到,所以 handleSearch 函数将它 attach 到 // 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) if err != nil { http.Error(w,err.Error(),http.StatusBadRequest) return } ctx = userip.NewContext(ctx,userIP) 处理函数带着 // Run the Google search and print the results. start := time.Now() results,err := google.Search(ctx,query) 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 包userip 包提供了两个功能:
一个 为了避免 key 冲突, // 为了避免与其他包中的 `Context` key 冲突 // 这里不输出 key 类型 (首字母小写) type key int // userIPKey 是 user IP 的 `Context` key // 它的值是随意写的。如果这个包中定义了其他 // `Context` key,这些 key 必须不同 const userIPKey key = 0 函数 func FromRequest(req *http.Request) (net.IP,error) { ip,_,err := net.SplitHostPort(req.RemoteAddr) if err != nil { return nil,fmt.Errorf("userip: %q is not IP:port",req.RemoteAddr) } 函数 func NewContext(ctx context.Context,userIP net.IP) context.Context { return context.WithValue(ctx,userIPKey,userIP) } 函数 func FromContext(ctx context.Context) (net.IP,bool) { // ctx.Value returns nil if ctx has no value for the key; // the net.IP type assertion returns ok=false for nil. userIP,ok := ctx.Value(userIPKey).(net.IP) return userIP,ok } 深入 google 包函数 Google Web Search API 请求包含 query 关键字和 user IP 两个参数。具体实现如下: func Search(ctx context.Context,query string) (Results,error) { // Prepare the Google Search API request. req,err := http.NewRequest("GET","https://ajax.googleapis.com/ajax/services/search/web?v=1.0",nil) if err != nil { return nil,err } q := req.URL.Query() q.Set("q",query) // If ctx is carrying the user IP address,forward it to the server. // Google APIs use the user IP to distinguish server-initiated requests // from end-user requests. if userIP,ok := userip.FromContext(ctx); ok { q.Set("userip",userIP.String()) } req.URL.RawQuery = q.Encode() 函数 var results Results err = httpDo(ctx,req,func(resp *http.Response,err error) error { if err != nil { return err } defer resp.Body.Close() // Parse the JSON search result. // https://developers.google.com/web-search/docs/#fonje var data struct { ResponseData struct { Results []struct { TitleNoFormatting string URL string } } } if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { return err } for _,res := range data.ResponseData.Results { results = append(results,Result{Title: res.TitleNoFormatting,URL: res.URL}) } return nil }) // httpDo waits for the closure we provided to return,so it's safe to // read results here. return results,err 函数 func httpDo(ctx context.Context,req *http.Request,f func(*http.Response,error) error) error { // Run the HTTP request in a goroutine and pass the response to f. tr := &http.Transport{} client := &http.Client{Transport: tr} c := make(chan error,1) go func() { c <- f(client.Do(req)) }() select { case <-ctx.Done(): tr.CancelRequest(req) <-c // Wait for f to return. return ctx.Err() case err := <-c: return err } } 在自己的代码中使用
|