Go language concurrency Model: using context

Source: Internet
Author: User
This is a creation in Article, where the information may have evolved or changed.

Brief introduction

In the go HTTP package server, each request has a corresponding goroutine to handle. The request handler function typically launches additional goroutine to access the backend services, such as the database and the RPC service. The goroutine used to process a request usually requires access to some data that is specific to the request, such as the authentication information of the end user, the authentication token, and the deadline for the request. When a request is canceled or timed out, all the goroutine that are used to process the request should exit quickly before the system can release the resources that these goroutine occupy.

Inside Google, we've developed Context packages that are designed to simplify the processing of data, cancellation signals, deadlines, and so on for multiple goroutine that process a single request, which can involve multiple API calls. You can go get golang.org/x/net/context get this package by command. What this article says is that if you use this package, you will also provide a complete example.

Read suggestions

This article covers the done channel, and if you don't understand the concept, read the "Go language concurrency Model: using channel as a UNIX pipe."

Since access golang.org/x/net/context requires a ladder, you can access it on GitHub on the mirror.
If you want to download the code in this article, you can see the "Related LINKS" link at the end of the article.

Package context

The core of the context package is the struct context, which declares the following:

// 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{}}

Note: Here we have simplified the description, a more detailed description of the view Godoc:context

Donemethod returns a channel, which Context is a cancellation signal for functions that run in a way. When this channel is closed, the functions mentioned above should terminate the work at hand and return immediately. After that, the Err method returns an error telling you why it Context was canceled. Donemore details on Channel View previous article "Go language concurrency Model: using Channel as UNIX pipe".

One Context cannot have a Cancel method, and we can only channel it to Done receive data. The reason behind this is consistent: the function that receives the cancellation signal and the function that sends the signal is usually not one. A typical scenario is that the parent operation starts Goroutine for a child action operation, and the child action cannot cancel the parent operation. As a compromise, the WithCancel function (which is discussed later) provides a way to cancel the new one Context .

ContextThe object is thread-safe, and you can Context pass an object to any number of gorotuine,
When the cancel operation is performed on it, all Goroutine will receive a cancellation signal.

Deadlinemethod allows the function to determine whether they should start working. If there is too little time left, perhaps these functions are not worth starting. Code, we can also use Deadline objects to set deadlines for I/O operations.

ValueMethod allows an Context object to carry data from the request scope, which must be thread-safe.

Inherit context

The context package provides functions to assist users in Context creating new objects from existing objects Context .
These Context objects form a tree: When an Context object is canceled, all inherited from it Context will be canceled.

Backgroundis Context the root of all object trees and it cannot be canceled. It declares the following:

// 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

WithCanceland WithTimeout functions return inherited Context objects that can be canceled earlier than their parents Context .

When the request handler function returns, the request is associated with a Context cancellation. When you use multiple replicas to send a request, you can use WithCancel the Cancel extra request. WithTimeoutuseful when setting a deadline for back-end server requests. Here are the declarations of these three functions:

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

WithValueThe function can relate the data of the request scope to the Context object. The statement reads as follows:

// WithValue returns a copy of parent whose Value method returns val for key.func WithValue(parent Context, key interface{}, val interface{}) Context

Of course, to know Context how the package works, the best way is to look at a chestnut.

A chestnut: Google Web Search

Our example is an HTTP service that can forward a similar /search?q=golang&timeout=1s request to a
Google Web Search API, and then render the returned results. timeoutThe parameter is used to tell the server when the request is canceled.

The code for this example is stored in three packages:

    1. Server: It provides the main function and handles /search the HTTP handler

    2. Userip: It is able to parse the user's IP from the request and bind the request to an Context object.

    3. Google: It contains the Search function, which is used to send a request to Google.

Drill down into the server program

The server program processes similar /search?q=golang requests and returns search results for the Google API. It handleSearch registers the function with the /search route. The handler function creates a Context ctx and initializes it to ensure that the Context handler returns when canceled. If the requested URL parameter is included timeout , it timeout Context is automatically canceled when it expires.
The code for Handlesearch is as follows:

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 function (handlesearch) resolves the query parameter from the request and then resolves the client IP through the Userip package. The Client IP is used here when the backend sends the request, so the Handlesearch function attach it to the Context object ctx. The code is as follows:

// 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 takes Context ctx the object and query calls with google.Search the code as follows:

// 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 processing function renders the search results, with the following code:

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}

Deep Userip Package

The Userip package offers two features:

    1. Resolves the client IP from the request;

    2. Associates the Client IP to an Context object.

An Context object provides a key-value mapping, both the type of key and value are interface{}, but the key must satisfy the equivalence (which can be compared) and value must be thread safe. A package similar to userip the one that hides the details of the mapping provides a Context strongly typed access to a particular type.

To avoid key collisions, userip a non-output type is defined key , and a value of that type is used as Context the key. The code is as follows:

// 为了避免与其他包中的 `Context` key 冲突// 这里不输出 key 类型 (首字母小写)type key int// userIPKey 是 user IP 的 `Context` key// 它的值是随意写的。如果这个包中定义了其他// `Context` key,这些 key 必须不同const userIPKey key = 0

The function is FromRequest used from an HTTP. The Request object resolves the userip:

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

NewContextthe function returns a new Context object, which is userip by the carrier:

func NewContext(ctx context.Context, userIP net.IP) context.Context {    return context.WithValue(ctx, userIPKey, userIP)}

The function FromContext Context parses userip from an object:

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}

Dive into Google Pack

function google.Search to send an HTTP request to the Google Web Search API and parse the returned JSON data. The function receives an Context object CTX as the first parameter, and once the request has not been returned, ctx.Done the function returns immediately if it is closed.

The Google Web Search API request contains the query keyword and user IP two parameters. The specific implementation is as follows:

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

The function Search uses an auxiliary function httpDo to send an HTTP request and ctx.Done cancel the request when it is closed (if the request is still being processed or returned). The function is Search passed to httpDo a closure to handle HTTP results. Here are the specific implementations:

var results Resultserr = 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 function httpDo sends HTTP requests and processing results in a new goroutine. If it ctx.Done is already closed and the goroutine of the processing request still exists, then the request is canceled. Here are the specific implementations:

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    }}

Use in your own codeContext

Many server frameworks provide the packages and types that manage request scope data. We can define an Context implementation of an interface,
Glue the code that already has Context the code and the expected parameters together.

For a chestnut, the Gorilla Framework's Github.com/gorilla/context package allows the handler function (handlers) to combine the data and the request with the HTTP request to the Key-value pair's mapping. In Gorilla.go, we provide a Context concrete implementation where the value returned by this implementation is associated with a specific HTTP request in the gorilla package.

There are also some packages that implement a similar Context cancellation mechanism. For example, there is a Kill method in Tomb, which sends a Dying cancellation signal by closing the channel named. Tombalso provides a way to wait for the goroutine to exit, similar to the sync.WaitGroup . In Tomb.go, we provide an Context implementation when its parent Context is canceled
Or Tomb when an object is killed, the Context object is also canceled.

Conclusion

At Google, we ask the Go programmer to Context pass each function as the first parameter to the portal request and the export request link. This mechanism ensures that the go projects developed by multiple teams work well together, on the other hand, it is a simple timeout and cancellation mechanism, which ensures that critical section data (such as security credentials) can be transferred smoothly in different go projects.

If you are Context building a server framework on top, you need an implementation of your own to Context Context build a bridge between the framework and the code of the desired parameters.
Of course, the Client library also needs to receive an Context object. After a common interface between the request scope data and the cancellation is established, the developer uses the Context
It's easy to share code and create scalable services.

Original Author: Sameer Ajmani Translator: Oscar

Next preview: Go language concurrency Model: Use Select (original link).

RELATED LINKS

    1. Original link

    2. Code location

    3. Code location (Mirror)

    4. Mirror of the package net

    5. Google Web Search API

Sweep code attention to the public number "go language deep"

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.