2020-11-30-golang同時モードcontext

9888 ワード


https://blog.golang.org/context

Goの同時モード:Context(コンテキスト)


Goのサーバでは,それぞれの着信要求は独立したgoroutineによって処理される.要求を処理するgoroutineでは、通常、データベースおよびRPCサービスにアクセスするために追加のgoroutinesが起動します.同じリクエストに存在するgoroutinesのグループは、通常、端末ユーザのアイデンティティ識別、認証トークン、リクエストの締め切り日など、特定のリクエスト値にアクセスする必要があります.1つのリクエストがキャンセルまたは超過された場合、このリクエストに関連するすべてのgoroutinesは、システムが関連リソースを回収するために迅速に終了する必要があります.
Googleはcontextのパッケージを開発し、1つの処理要求のすべてのgoroutineでAPI境界にまたがって要求範囲内の値を伝達し、信号をキャンセルし、締め切り時間を便利にする(makes it easy to pass request-scoped values,cancelation signals,and deadlines across API boundaries to all the goroutines involved in handling a request.).

Context

//  Context  Deadline, cancelation signal,   request-scoped values
// across API boundaries.  goroutines 
type Context interface {
    // Done channel, Context , closed。
    Done() 

Doneメソッドは、Contextで実行される相関関数のキャンセル信号としてチャネルを返し、チャネルが閉じた場合、相関関数はそれらの動作を放棄して返す必要があります.ErrメソッドはContextがなぜキャンセルされたのかを表すエラーを返します.この時、Done channelの詳細を紹介する文章があります.
ContextにはCancelの方法はありません.Done channelが受信のみである理由と同じです.キャンセル信号を受信する関数は通常送信信号関数ではありません.特に、親操作が子操作のgoroutinesを起動する場合、これらの子操作は親操作をキャンセルできないはずです.逆に、WithCancel関数は、新しいContext値を取り消す方法を提供します.
Contextはマルチgoroutineで安全です.コードでは、1つのContextを複数のgoroutineに渡したり、Contextをキャンセルしてgoroutineに通知したりすることができます.

派生Derived contexts


contextパッケージは、既存のcontext値から新しいContext値を派生させる機能を提供します.木を形成しますContextがキャンセルされると、派生したすべてのContextsがキャンセルされます.
// Background  (empty) Context.  , deadline, values。
// Background is typically used in main, init, and tests,
// and as the top-level Context for incoming requests.
func Background() Context

BackgroudはContextツリーのルートであり、キャンセルされません.
WithCancelとWithTimeoutが返す派生contextは、親contextよりも早くキャンセルできます.要求処理が返されると、この要求に関連するContextは通常キャンセルされる.WithCancelは冗長リクエストのキャンセルにも役立ちます.WithTimeoutは、バックエンドサーバに対する要求のdeadlineを設定するのに役立ちます.
// WithCancel  Context , Context Done   cancel , Context Done 。
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検索


私たちのこの例ではhttpサービス側であり、/search?q=golang&timeout=1sのようなURLsを処理し、検索キーワード「golang」をGoogle Web Search APIに転送し、結果をレンダリングする.タイムアウトパラメータは、サーバに持続時間が経過した後にリクエストをキャンセルするように伝えます.
コードは3つのパケットにまたがる(The code is split across three packages:)
  • serverはmain関数および/searchの処理関数(handler)
  • を提供する
  • useripは、要求からユーザIPアドレスを抽出し、コンテキストに関連付ける機能を提供する.
  • googleはSearch関数を提供し、Googleにクエリーを送信します.

  • Serverプログラム

    package main
    ​
    import (
        "context"
        "html/template"
        "log"
        "net/http"
        "time"
    ​
        "golang.org/x/blog/content/context/google"
        "golang.org/x/blog/content/context/userip"
    )
    ​
    func main() {
        http.HandleFunc("/search", handleSearch)
        log.Fatal(http.ListenAndServe(":8080", nil))
    }
    ​
    // handleSearch handles URLs like /search?q=golang&timeout=1s by forwarding the
    // query to google.Search. If the query param includes timeout, the search is
    // canceled after that duration elapses.
    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)
        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 != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        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
        }
    }
    ​
    var resultsTemplate = template.Must(template.New("results").Parse(`
    
    
    
      
       { {range .Results}}    
    1. { {.Title}} - { {.URL}}
    2.  { {end}}  
     

    { {len .Results}} results in { {.Elapsed}}; timeout { {.Timeout}}

    `))

    useripパッケージ

    package userip
    ​
    import (
        "context"
        "fmt"
        "net"
        "net/http"
    )
    ​
    // FromRequest  req IP ,  
    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)
        }
    ​
        userIP := net.ParseIP(ip)
        if userIP == nil {
            return nil, fmt.Errorf("userip: %q is not IP:port", req.RemoteAddr)
        }
        return userIP, nil
    }
    ​
    // key , key 。got it
    type key int
    ​
    // userIPkey  IP context key,  。
    //  context keys,  。
    const userIPKey key = 0
    ​
    // NewContext returns a new Context carrying userIP.
    func NewContext(ctx context.Context, userIP net.IP) context.Context {
        return context.WithValue(ctx, userIPKey, userIP)
    }
    ​
    // FromContext extracts the user IP address from ctx, if present.
    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パッケージ

    package google
    ​
    import (
        "context"
        "encoding/json"
        "net/http"
    ​
        "golang.org/x/blog/content/context/userip"
    )
    ​
    // Results is an ordered list of search results.
    type Results []Result
    ​
    // A Result contains the title and URL of a search result.
    type Result struct {
        Title, URL string
    }
    ​
    // Search sends query to Google search and returns the results.
    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()
    ​
        // Issue the HTTP request and handle the response. The httpDo function
        // cancels the request if ctx.Done is closed.
        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
    }
    ​
    // httpDo issues the HTTP request and calls f with the response. If ctx.Done is
    // closed while the request or f is running, httpDo cancels the request, waits
    // for f to exit, and returns ctx.Err. Otherwise, httpDo returns f's error.
    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.
        c := make(chan error, 1)
        req = req.WithContext(ctx)
        go func() { c 

    まとめ


    Googleでは、Goプログラマーに、転送要求と転送要求の間の呼び出しパスで、Contextを各関数の最初のパラメータとして渡すように要求します.これにより、多くの異なるチームが開発したGoコードが相互運用をうまく行うことができます.タイムアウトとキャンセルの簡単な制御を提供し、セキュリティ証明書などのキー値がGoプログラムに正しく伝達されることを保証します.