1. 程式人生 > >Go Concurrency Patterns: Context

Go Concurrency Patterns: Context

29 July 2014

Introduction

In 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 package that makes it easy to pass request-scoped values, cancelation signals, and deadlines across API boundaries to all the goroutines involved in handling a request. The package is publicly available as context. This article describes how to use the package and provides a complete working example.

Context

The core of the context package is the Context type:

// 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 Done method returns a channel that acts as a cancelation signal to functions running on behalf of the Context: when the channel is closed, the functions should abandon their work and return. The Err method returns an error indicating why the Context was canceled. The Pipelines and Cancelation article discusses the Done channel idiom in more detail.

A Context does not have a Cancel method for the same reason the Done channel is receive-only: the function receiving a cancelation signal is usually not the one that sends the signal. In particular, when a parent operation starts goroutines for sub-operations, those sub-operations should not be able to cancel the parent. Instead, the WithCancel function (described below) provides a way to cancel a new Context value.

A Context is safe for simultaneous use by multiple goroutines. Code can pass a single Context to any number of goroutines and cancel that Context to signal all of them.

The Deadline method allows functions to determine whether they should start work at all; if too little time is left, it may not be worthwhile. Code may also use a deadline to set timeouts for I/O operations.

Value allows a Context to carry request-scoped data. That data must be safe for simultaneous use by multiple goroutines.

Derived contexts

The context package provides functions to derive new Context values from existing ones. These values form a tree: when a Context is canceled, all Contexts derived from it are also canceled.

Background is the root of any Context tree; it is never canceled:

// 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 and WithTimeout return derived Context values that can be canceled sooner than the parent Context. The Context associated with an incoming request is typically canceled when the request handler returns. WithCancel is also useful for canceling redundant requests when using multiple replicas. WithTimeout is useful for setting a deadline on requests to backend servers:

// 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 provides a way to associate request-scoped values with a Context:

// 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 context package is through a worked example.

Example: Google Web Search

Our example is an HTTP server that handles URLs like /search?q=golang&timeout=1s by forwarding the query "golang" to the Google Web Search API and rendering the results. The timeout parameter tells the server to cancel the request after that duration elapses.

The code is split across three packages:

  • server provides the main function and the handler for /search.
  • userip provides functions for extracting a user IP address from a request and associating it with a Context.
  • google provides the Search function for sending a query to Google.

The server program

The server program handles requests like /search?q=golang by serving the first few Google search results for golang. It registers handleSearch to handle the /search endpoint. The handler creates an initial Context called ctx and arranges for it to be canceled when the handler returns. If the request includes the timeout URL parameter, the Context is canceled automatically when the timeout 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.

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 google.Search with ctx and the query:

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

The userip package provides functions for extracting a user IP address from a request and associating it with a Context. A Context provides a key-value mapping, where the keys and values are both of type interface{}. Key types must support equality, and values must be safe for simultaneous use by multiple goroutines. Packages like userip hide the details of this mapping and provide strongly-typed access to a specific Context value.

To avoid key collisions, userip defines an unexported type key and uses a value of this type as the context key:

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

FromRequest extracts a userIP value from an http.Request:

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 returns a new Context that carries a provided userIP value:

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

FromContext extracts a userIP from a Context:

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

The google.Search function makes an HTTP request to the Google Web Search API and parses the JSON-encoded result. It accepts a Context parameter ctx and returns immediately if ctx.Done is closed while the request is in flight.

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

Search uses a helper function, httpDo, to issue the HTTP request and cancel it if ctx.Done is closed while the request or response is being processed. Search passes a closure to httpDo handle the HTTP response:

    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 httpDo function runs the HTTP request and processes its response in a new goroutine. It cancels the request if ctx.Done is closed before the goroutine exits:

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 Contexts

Many server frameworks provide packages and types for carrying request-scoped values. We can define new implementations of the Context interface to bridge between code using existing frameworks and code that expects a Context parameter.

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 Context implementation whose Value method returns the values associated with a specific HTTP request in the Gorilla package.

Other packages have provided cancelation support similar to Context. For example, Tomb provides a Kill method that signals cancelation by closing a Dying channel. Tomb also provides methods to wait for those goroutines to exit, similar to sync.WaitGroup. In tomb.go, we provide a Context implementation that is canceled when either its parent Context is canceled or a provided Tomb is killed.

Conclusion

At Google, we require that Go programmers pass a Context parameter as the first argument to every function on the call path between incoming and outgoing requests. This allows Go code developed by many different teams to interoperate well. It provides simple control over timeouts and cancelation and ensures that critical values like security credentials transit Go programs properly.

Server frameworks that want to build on Context should provide implementations of Context to bridge between their packages and those that expect a Context parameter. Their client libraries would then accept a Context from the calling code. By establishing a common interface for request-scoped data and cancelation, Context makes it easier for package developers to share code for creating scalable services.

相關推薦

Go Concurrency Patterns: Context

29 July 2014 Introduction In Go servers, each incoming request is handled in its own goroutine. Request ha

16 Go Concurrency Patterns: Timing out, moving on

allocated mea AMM condition package rev fun min example Go Concurrency Patterns: Timing out, moving on 23 September 2010 Concurrent prog

Go Concurrency Patterns: Pipelines and cancellation

13 March 2014 Introduction Go's concurrency primitives make it easy to construct streaming data pipelines

Go Concurrency Patterns: Timing out, moving on

23 September 2010 Concurrent programming has its own idioms. A good example is timeouts. Although Go's chann

Go語言-Context上下文實踐

opera and lur deadline dst turn function 存在 AI 使用 Context 的程序包需要遵循如下的原則來滿足接口的一致性以及便於靜態分析 1.不要把 Context 存在一個結構體當中,顯式地傳入函數。Context 變量需要作為第一

Go 殺器Context

nbsp 狀態 存在 之前 共享 bubuko 上下 接收 tex Context通常被譯作上下文,它是一個比較抽象的概念。在討論鏈式調用技術時也經常會提到上下文。一般理解為程序單元的一個運行狀態、現場、快照,而翻譯中上下又很好地詮釋了其本質,上下則是存在上下層的傳遞,上會

go上下文管理-context

context的應用 1 .ctx可以繼承 2 .超時控制 package main import ( "context" "fmt" "io/ioutil" "net/http" "time" ) type Result struct { r

深度解密Go語言之context

目錄 什麼是 context 為什麼有 context context 底層實現原理 整體概覽 介面 Context canceler

圖解Go語言的context瞭解程式語言核心實現原始碼

基礎築基 基於執行緒的程式語言中的一些設計 ThreadGroup ThreadGroup是基於執行緒併發的程式語言中常用的一個概念,當一個執行緒派生出一個子執行緒後通常會加入父執行緒的執行緒組(未指定執行緒組的情況下)中, 最後可以通過ThreadGroup來控制一組執行緒的退出等操作, 然後在go語言

Go語言的context包從放棄到入門

[toc] 以下內容由偉大的詩人、哲學家chenqionghe吐血傳授,希望能幫助到你,謝謝~ # 一、Context包到底是幹嘛用的 我們會在用到很多東西的時候都看到context的影子,比如gin框架,比如grpc,這東西到底是做啥的? 大家都在用,沒幾個知道這是幹嘛的,知其然而不知其所以然 >誰都在C

Go語言Context(設計及分析)

sin 循環 elf 處理請求 val pri inter text ont context簡單概述:Go服務器的每個請求都有自己的goroutine,而有的請求為了提高性能,會經常啟動額外的goroutine處理請求,當該請求被取消或超時,該請求上的所有goroutine

go context包的WithTimeout和WithCancel的使用

tex api fmt can wait 主動 sync select 返回 1、WaitGroup   它是一種控制並發的方式,它的這種方式是控制多個goroutine同時完成。 func main() { var wg sync.WaitGroup wg

Go並發控制--context的使用

el函數 text one ask Go default with block main 並發控制 Cancel Example 通過使用WithCancel可以取消一個或多個goroutine的執行,以實現對並發的控制。 package main import (

go get的報錯unrecognized import path "golang.org/x/net/context"處理方法

問題原因是我在ubuntu 18.04上用apt geti安裝的go的sdk,而GOPATH設定,net包的安裝的工作沒做,欠的工作還是要還的. 具體處理方法如下: apt install golang-go 上面是我安裝go環境的方法. 之後用go get的時候,會報錯如下:

理解GO CONTEXT機制

1 什麼是Context 最近在公司分析gRPC原始碼,proto檔案生成的程式碼,介面函式第一個引數統一是ctx context.Context介面,公司不少同事都不瞭解這樣設計的出發點是什麼,其實我也不瞭解其背後的原理。今天趁著妮妲颱風妹子正面登陸深圳,全市停工、停課、停業,在家休息找了

Context propagation over HTTP in Go

https://medium.com/@rakyll/context-propagation-over-http-in-go-d4540996e9b0 Context propagation over HTTP in Go Go 1.7 introduced a built-in

[GO]go context的deadline方法

package main import ( "time" "context" "fmt" ) func main() { d := time.Now().Add(50*time.Millisecond) ctx, cancel := context.WithD

Go語言 —— Go Context

控制併發有兩種經典的方式,一種是WaitGroup,另外一種就是Context,今天我就談談Context。 什麼是WaitGroup WaitGroup以前我們在併發的時候介紹過,它是一種控制併發的方式,它的這種方式是控制多個goroutine同時完成。 func

go context剖析之使用技巧

context背景 因為goroutine,go的併發非常方便,但是這也帶來了另外一個問題,當我們進行一個耗時的非同步操作時,如何在約定的時間內終止該操作並返回一個自定義的結果?這也是大家常說的我們如何去終止一個goroutine(因為goroutine不同於os執行緒,沒有主動interrupt機制),這

go context剖析之原始碼分析

開篇 原始碼面前,了無祕密。本文作為context分析系列的第二篇,會從原始碼的角度來分析context如何實現所承諾的功能及內在特性。本篇主要從以下四個角度闡述: context中的介面、context有哪些型別、context的傳遞實現、context的層級取消觸發實現。 context中的介面 上