Go翻訳文のcontextによる同時制御

11300 ワード

作者:Sameer Ajmani|住所:blog.golang.org/context

翻訳者の前言


第2編の公式ブログの翻訳は、主にGo同時制御のcontextパッケージについてです.
全体的に、前回こそGo同時の基礎と核心だと思います.contextは前章の基礎の上でgoroutine制御のために開発された使いやすいライブラリです.結局、異なるgoroutineの間でdone channelだけを伝えるのは、確かに情報量が少なすぎる.
文章はcontextが提供する方法を簡単に紹介し、それらの使用方法を簡単に紹介した.次に,探索の一例を通して,実シーンでの使用を紹介した.
文章の末尾の部分は、公式に実現されたcontextのほかに、githubのような第三者の実現もあることを説明した.com/contextとTombですが、これらは公式contextが登場してから更新を停止しています.実は原因は簡単で、結局一般的には公式がもっと強いです.以前はgoモジュール管理も百花斉放だったが、最近は公式に独自のソリューションを発表し、間もなく他の方法が淘汰されるかもしれない.
実は、この文章は読みにくいと思います.順序を追って進まないような気がします.突然の例は少し愚かになるかもしれません.
翻訳の本文は以下の通りです.
Goのサービスでは、各リクエストには独立したgoroutine処理があり、各goroutineは通常、データベースやRPCサービスへのアクセスなど、新しいgoroutineを起動して追加の作業を実行します.同リクエスト内のgoroutineは、ユーザ認証、認証token、リクエスト締め切り時間など、リクエストデータアクセスを共有できる必要があります.リクエストがキャンセルまたはタイムアウトする場合、リクエスト範囲内のすべてのgoroutineは直ちに終了し、リソース回収を行う.
Googleではcontextのパッケージを開発し、リクエスト内のgoroutine間でリクエストデータ、キャンセル信号、タイムアウト情報を簡単に伝えることができます.詳細はcontextを参照してください.
この文書ではcontextパッケージの使用について詳しく説明し、完全な使用例を提供します.

Context


contextのコアはContextタイプです.次のように定義します.
// A Context carries a deadline,cancellation signal,and request-scoped values
// across API. Its methods are safe for simultaneous use by multiple goroutines
//   Context   API ( )  、 、 。
// Context  。
type Context interface {
    // Done returns a channel that is closed when this context is cancelled
    // or times out.
    // Done   channel,  context  ,Done  。
    Done() 

説明は簡単で、詳細はgodocを参照してください.
Doneメソッドはcontextのキャンセル信号を受信するために使用できるchannelを返す.チャンネルが閉じると、Done信号を傍受する関数は、現在実行中の作業をすぐに放棄し、戻ります.Err法はerror変数を返し,その中からcontextがなぜ取り消されたのかを知ることができる.pipeline and cancelationではDone channelについて詳しく紹介した.
なぜContextにはcancelメソッドがないのか、その理由はDone channel読み取り専用の理由と似ている.すなわち、キャンセル信号を受信したgoroutineはキャンセル信号の送信を担当しない.特に、親が子goroutineを起動して操作を実行すると、子は親をキャンセルできません.逆に、WithCancelメソッド(後述)は、新しく作成したContextをキャンセルする方法を提供します.
Contextはコンカレントで安全です.Contextは任意の数のgoroutineに渡すことができ、cancelを通じてすべてのgoroutineに信号を送信することができます.
Deadlineメソッドでは、関数に作業を開始する必要があるかどうかを決定することができます.残りの時間が短すぎると、作業を開始する価値はありません.コードではdeadlineでIO操作にタイムアウト時間を設定できます.
Valueメソッドは、contextがgoroutine間で要求範囲内のデータを共有することができ、これらのデータはコヒーレントで安全である必要がある.

派生Context


contextパッケージは、既存のContextインスタンスから新しいContextを派生させる複数の関数を提供します.これらのContextは、1つのContextがキャンセルされると、派生したcontextがキャンセルされるツリー構造を形成します.
Background関数が返すContextは、任意のContextルートであり、キャンセルできません.
// 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.
// Background   Context, , , 。Background   main、init   tests  。
func Background() Context

WithCancelとWithTimeoutでは、親よりも前に取り消された新しいContextインスタンスが派生します.リクエストに関連付けられたContextインスタンスは、リクエスト処理が完了するとキャンセルされます.複数コピーのデータ要求が発生した場合、WithCancelを使用して余分な要求をキャンセルできます.バックエンド・サービスを要求するときに、WithTimeoutを使用してタイムアウト時間を設定できます.
// WithCancel returns a copy of parent whose Done channel is closed as soon as
// parent.Done is closed or cancel is called.
// WithCanal   Context  ,  Done channel   cancel,  Done channel  。
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)

// A CancelFunc cancels a Context.
// CancelFunc   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.
//   Context   CancelFunc, ,  Done  ,  Done  ,cancel  , 。
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

WithValueは、Contextを介して要求に関連するデータを転送する方法を提供します.
// WithValue returns a copy of parent whose Value method returns val for key.
func WithValue(parent Context, key interface{}, val interface{}) Context

contextはどのように使いますか?最良の方法は、1つのケースを通じてプレゼンテーションします.

ケース:Google Web検索


HTTPサービスを実装し、類似/searchを処理するケースを示します.q=golang&timeout=1 sのリクエストです.timeoutは、要求処理時間が指定時間を超えた場合、実行をキャンセルすることを示す.
コードは主に3つのpackageに関連し、それぞれ:
  • server、メイン関数エントリおよび/search処理関数;
  • useripは、requestのcontextからuser ipを導出する共通関数を実現する.
  • googleは、検索要求をGoogleに送信するSearch関数を実現した.

  • 紹介開始!

    Package server


    サーバは類似/searchの処理を担当しますか?q=golangのリクエストはgolang検索結果を返し、handleSearchは実際の処理関数であり、ctxと名付けられたContextを最初に初期化し、defer実装関数によってcancelを終了する.要求パラメータにtimeoutが含まれている場合は、WithTimeoutでcontextを作成し、タイムアウト後にContextは自動的にキャンセルされます.
    func handleSearch(w http.ResponseWriter, req *http.Request) {
        // ctx is the Context for this handler. Calling cancel closes the
        // cxt.Done channel, which is the cancellation signal for requests
        // started by this handler
        var (
            ctx context.Context
            cancel context.Context
        )
    
        timeout, err := time.ParseDuration(req.FromValue("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 handlSearch returns.

    次に、処理関数はリクエストからクエリーキーワードとクライアントIPを取得し、クライアントIPの取得はuseripパケット関数を呼び出すことによって実現される.また、バックエンドサービスの要求にもクライアントIPが必要であるため、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.FormRequest(req)
        if err != nil {
            http.Error(w, e.Error(), http.StatusBadRequest)
            return
        }
    
        ctx = userip.NewContext(ctx, userIP)

    Googleを呼び出すSearchは、ctxとqueryパラメータを入力します.
        // Run the Google search and print the results
        start := time.Now()
        results, err := google.Search(ctx, query)
        elapsed := time.Since(start)

    検索に成功すると、handlerレンダリング結果ページが表示されます.
        if err := resultsTemplate.Execute(w, struct{
            Results     google.Results
            Timeout, Elapsed time.Duration
        }{
            Results: results,
            Timeout: timeout,
            Elaplsed: elaplsed,
        }); err != nil {
            log.Print(err)
            return
        }

    Package userip


    useripパッケージには、要求からユーザIPをエクスポートし、ContextにユーザIPをバインドする2つの関数が用意されています.Contextにはkey-valueマッピングが含まれており、keyとvalueのタイプはすべてinterface{}であり、keyは等しい比較をサポートする必要があります.valueはコンカレントで安全です.useripパケットはContextのvalue,すなわちclient IPに対してタイプ変換を実行することによってmapの詳細を隠す.keyの競合を回避するために、useripはエクスポートできないタイプkeyを定義します.
    // The key type is unexported to prevent collision with context keys defined in
    // other package
    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 userIPKye key = 0

    関数FromRequestはhttpから担当します.Requestエクスポートユーザ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)
        }

    関数NewContextは、userIPを含むContextを生成します.
    func NewContext(ctx context.Context, userIP net.IP) context.Context {
        return context.WithValue(ctx, userIPKey, userIP)
    }

    FromContextはContextから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 google


    google.SearchはGoogle Web Searchインタフェースのリクエストと、インタフェースがJSONデータを返す解析を担当します.Contextタイプのパラメータctxを受信します.ctxの場合.Doneがクローズし、要求が実行中であってもすぐに戻ります.
    クエリの要求パラメータには、queryキーワードとユーザIPが含まれます.
    func Search(ctx context.Context, query string) (Results, error) {
        // Prepare the Google Search API request
        req, err := http.NewRequest("GET", "http://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-users requests
        if userIP, ok := userip.FromContext(ctx); ok {
            q.Set("userip", userIP.String())
        }
        req.URL.RawQuery = q.Encode()

    Search関数は、ctx.Doneはクローズし、リクエストが実行中であってもクローズされます.Searchはhttp Doに応答結果を処理するための閉パケット関数を渡した.
        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
        })
    
        return results, err

    httpDo関数は、HTTPリクエストの実行と応答結果の処理を担当する新しいgoroutineを開きます.goroutineが終了する前に要求が完了していない場合、ctx.Doneが閉じ、要求実行がキャンセルされます.
    func httpDo(ctx context.Context, req *http.Request, f func(*http.Request, 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 

    Contextベース調整コード


    多くのサービス・エンド・フレームワークでは、要求されるパケットとデータ・タイプのデータ転送が提供されます.Contextインタフェースに基づいて新しい実装コードを記述し,フレームワークと処理関数の接続を完了することができる.
    以下では、開発した2つのcontextのサードパーティ実装について説明します.その中には、簡単に理解しなければならないものもあります.
    例えば、Gorilla'sのcontextは、要求にkey valueマッピングを提供することによって関連データバインディングを実現する.ここにいるよgoは、Contextの実装を提供し、そのValueメソッドが返す値は、特定のHTTPリクエストに関連付けられる.
    他のパッケージでは、Contextと同様のキャンセルサポートが提供されています.例えば、Tombには、Dying channelを閉じることによってキャンセル信号の送信を実現するKill方法がある.Tombもgoroutineの終了を待つ方法を提供し、sync.WaitGroupは似ています.tomb.goでは、親Contextがキャンセルされた場合、またはTombがkillされた場合、現在のContextがキャンセルされる実装が提供される.

    まとめ


    Googleでは、リクエストクラスを受信または送信する関数について、Contextを最初のパラメータとして渡す必要があります.これにより,異なるチームのGoコードでも良好に動作する.Contextはgoroutineのタイムアウトと制御のキャンセルを容易にし、セキュリティ証明書などの重要なデータの安全な伝達を確保します.
    ContextベースのサービスフレームワークはContextを実現する必要があり、フレームワークと使用者の接続を支援し、使用者はフレームからContextパラメータを受信することを望んでいる.一方、クライアント・ライブラリは、これとは逆に呼び出し元からContextパラメータを受信します.contextは、リクエストデータとキャンセル制御のための汎用インタフェースを構築することで、パッケージ開発者が自分のコードを簡単に共有し、より拡張性のあるサービスを構築することができます.