在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
https://blog.golang.org/context
IntroductionIn Go servers, each incoming request is handled in its own goroutine. Request handlers often start additional goroutines to access backends such as databases and RPC services. The set of goroutines working on a request typically needs access to request-specific values such as the identity of the end user, authorization tokens, and the request's deadline. When a request is canceled or times out, all the goroutines working on that request should exit quickly so the system can reclaim any resources they are using. At Google, we developed a Context The core of the // 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{} } (This description is condensed; the godoc is authoritative.)
The A A The
Derived contextsThe
// 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
The best way to see how to use the Example: Google Web SearchOur example is an HTTP server that handles URLs like The code is split across three packages:
The server programThe server program handles requests like 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. } The handler extracts the query from the request and extracts the client's IP address by calling on the userip package. The client's IP address is needed for backend requests, so handleSearch attaches it to ctx :// 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)
The handler calls // Run the Google search and print the results. start := time.Now() results, err := google.Search(ctx, query) elapsed := time.Since(start)
If the search succeeds, the handler renders the results: 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 }
Package useripThe userip package provides functions for extracting a user IP address from a request and associating it with a To avoid key collisions, // The key type is unexported to prevent collisions with context keys defined in // other packages. type key int // userIPkey is the context key for the user IP address. Its value of zero is // arbitrary. If this package defined other context keys, they would have // different integer values. 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 }
Package googleThe google.Search function makes an HTTP request to the Google Web Search API and parses the JSON-encoded result. It accepts a
The Google Web Search API request includes the search query and the user IP as query parameters: 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
The 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 } }
Adapting code for ContextsMany server frameworks provide packages and types for carrying request-scoped values. We can define new implementations of the For example, Gorilla's github.com/gorilla/context package allows handlers to associate data with incoming requests by providing a mapping from HTTP requests to key-value pairs. In gorilla.go, we provide a Other packages have provided cancelation support similar to ConclusionAt Google, we require that Go programmers pass a Server frameworks that want to build on
https://rakyll.org/leakingctx/ The context package makes it possible to manage a chain of calls within the same call path by signaling context’s Done channel. In this article, we will examine how to use the context package to avoid leaking goroutines. Assume, you have a function that starts a goroutine internally. Once this function is called, the caller may not be able to terminate the goroutine started by the function. // gen is a broken generator that will leak a goroutine. func gen() <-chan int { ch := make(chan int) go func() { var n int for { ch <- n n++ } }() return ch }
The generator above starts a goroutine with an infinite loop, but the caller consumes the values until n is equal to 5. // The call site of gen doesn't have a for n := range gen() { fmt.Println(n) if n == 5 { break } }
Once the caller is done with the generator (when it breaks the loop), the goroutine will run forever executing the infinite loop. Our code will leak a goroutine. We can avoid the problem by signaling the internal goroutine with a stop channel but there is a better solution: cancellable contexts. The generator can select on a context’s Done channel and once the context is done, the internal goroutine can be cancelled. // gen is a generator that can be cancellable by cancelling the ctx. func gen(ctx context.Context) <-chan int { ch := make(chan int) go func() { var n int for { select { case <-ctx.Done(): return // avoid leaking of this goroutine when ctx is done. case ch <- n: n++ } } }() return ch }
Now, the caller can signal the generator when it is done consuming. Once cancel function is called, the internal goroutine will be returned. ctx, cancel := context.WithCancel(context.Background()) defer cancel() // make sure all paths cancel the context to avoid context leak for n := range gen(ctx) { fmt.Println(n) if n == 5 { cancel() break } } // ...
The full program is available as a gist.
Context and Cancellation of goroutineshttp://dahernan.github.io/2015/02/04/context-and-cancellation-of-goroutines/ Yesterday I went to the event London Go Gathering, where all the talks had a great level, but particulary Peter Bourgon gave me idea to write about the excelent package context. Context is used to pass request scoped variables, but in this case I’m only going to focus in cancelation signals. Lets say that I have a program that execute a long running function, in this case package main import ( "fmt" "sync" "time" ) var ( wg sync.WaitGroup ) func work() error { defer wg.Done() for i := 0; i < 1000; i++ { select { case <-time.After(2 * time.Second): fmt.Println("Doing some work ", i) } } return nil } func main() { fmt.Println("Hey, I'm going to do some work") wg.Add(1) go work() wg.Wait() fmt.Println("Finished. I'm going home") } $ go run work.go Hey, I'm going to do some work Doing some work 0 Doing some work 1 Doing some work 2 Doing some work 3 ... Doing some work 999 Finished. I'm going home Now imagine that we have to call that package main import ( "fmt" "log" "time" ) func work() error { for i := 0; i < 1000; i++ { select { case <-time.After(2 * time.Second): fmt.Println("Doing some work ", i) } } return nil } func main() { fmt.Println("Hey, I'm going to do some work") ch := make(chan error, 1) go func() { ch <- work() }() select { case err := <-ch: if err != nil { log.Fatal("Something went wrong :(", err) } case <-time.After(4 * time.Second): fmt.Println("Life is to short to wait that long") } fmt.Println("Finished. I'm going home") } $ go run work.go Hey, I'm going to do some work Doing some work 0 Doing some work 1 Life is to short to wait that long Finished. I'm going home Now, is a little bit better because, the main execution doesn’t have to wait for But it has a problem, if my program is still running like for example a web server, even if I don’t wait for the function For cancelation of the goroutine we can use the context package. We have to change the function to accept an argument of type package main import ( "fmt" "sync" "time" "golang.org/x/net/context" ) var ( wg sync.WaitGroup ) func work(ctx context.Context) error { defer wg.Done() for i := 0; i < 1000; i++ { select { case <-time.After(2 * time.Second): fmt.Println("Doing some work ", i) // we received the signal of cancelation in this channel case <-ctx.Done(): fmt.Println("Cancel the context ", i) return ctx.Err() } } return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second) defer cancel() fmt.Println("Hey, I'm going to do some work") wg.Add(1) go work(ctx) wg.Wait() fmt.Println("Finished. I'm going home") } $ go run work.go Hey, I'm going to do some work Doing some work 0 Cancel the context 1 Finished. I'm going home This is pretty good!, apart that the code looks more simple to manage the timeout, now we are making sure that the function These examples are good to learn the basics, but let’s try to make it more real. Now the package main // Lazy and Very Random Server import ( "fmt" "math/rand" "net/http" "time" ) func main() { http.HandleFunc("/", LazyServer) http.ListenAndServe(":1111", nil) } // sometimes really fast server, sometimes really slow server func LazyServer(w http.ResponseWriter, req *http.Request) { headOrTails := rand.Intn(2) if headOrTails == 0 { time.Sleep(6 * time.Second) fmt.Fprintf(w, "Go! slow %v", headOrTails) fmt.Printf("Go! slow %v", headOrTails) return } fmt.Fprintf(w, "Go! quick %v", headOrTails) fmt.Printf("Go! quick %v", headOrTails) return } Randomly is going to be very quick or very slow, we can check that with $ curl http://localhost:1111/ Go! quick 1 $ curl http://localhost:1111/ Go! quick 1 $ curl http://localhost:1111/ *some seconds later* Go! slow 0 So we are going to make an http request to this server, in a goroutine, but if the server is slow we are going to Cancel the request and return quickly, so we can manage the cancellation and free the connection. package main import ( "fmt" "io/ioutil" "net/http" "sync" "time" "golang.org/x/net/context" ) var ( wg sync.WaitGroup ) // main is not changed func main() { ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() fmt.Println("Hey, I'm going to do some work") wg.Add(1) go work(ctx) wg.Wait() fmt.Println("Finished. I'm going home") } func work(ctx context.Context) error { defer wg.Done() tr := &http.Transport{} client := &http.Client{Transport: tr} // anonymous struct to pack and unpack data in the channel c := make(chan struct { r *http.Response err error }, 1) req, _ := http.NewRequest("GET", "http://localhost:1111", nil) go func() { resp, err := client.Do(req) fmt.Println("Doing http request is a hard job") pack := struct { r *http.Response err error }{resp, err} c <- pack }() select { case <-ctx.Done(): tr.CancelRequest(req) <-c // Wait for client.Do fmt.Println("Cancel the context") return ctx.Err() case ok := <-c: err := ok.err resp := ok.r if err != nil { fmt.Println("Error ", err) return err } defer resp.Body.Close() out, _ := ioutil.ReadAll(resp.Body) fmt.Printf("Server Response: %s\n", out) } return nil } $ go run work.go Hey, I'm going to do some work Doing http request is a hard job Server Response: Go! quick 1 Finished. I'm going home $ go run work.go Hey, I'm going to do some work Doing http request is a hard job Cancel the context Finished. I'm going home As you can see in the output, we avoid the slow responses from the server. In the client the tcp connection is canceled so is not going to be busy waiting for a slow response, so we don’t waste resources. Happy coding gophers!.
|
请发表评论