context Go language of the standard library

In the Server Go http packet, each request has a corresponding goroutine to handle. Request handler usually starts additional goroutine used to access back-end services, such as database and RPC services. For processing a request goroutine typically involve accessing the requested specific data, such as end-user identity authentication information, authentication token associated, the request deadline. When a request is canceled or expires, all to handle the request goroutine should quickly withdraw, then the system can release these goroutine occupied resources.

Why Context

A basic example

package main

import (
    "fmt"
    "sync"

    "time"
)

var wg sync.WaitGroup

// 初始的例子

func worker() {
    for {
        fmt.Println("worker")
        time.Sleep(time.Second)
    }
    // 如何接收外部命令实现退出
    wg.Done()
}

func main() {
    wg.Add(1)
    go worker()
    // 如何优雅的实现结束子goroutine
    wg.Wait()
    fmt.Println("over")
}

Global Variables way

package main

import (
    "fmt"
    "sync"

    "time"
)

var wg sync.WaitGroup
var exit bool

// 全局变量方式存在的问题:
// 1. 使用全局变量在跨包调用时不容易统一
// 2. 如果worker中再启动goroutine,就不太好控制了。

func worker() {
    for {
        fmt.Println("worker")
        time.Sleep(time.Second)
        if exit {
            break
        }
    }
    wg.Done()
}

func main() {
    wg.Add(1)
    go worker()
    time.Sleep(time.Second * 3) // sleep3秒以免程序过快退出
    exit = true                 // 修改全局变量实现子goroutine的退出
    wg.Wait()
    fmt.Println("over")
}

Channel mode

package main

import (
    "fmt"
    "sync"

    "time"
)

var wg sync.WaitGroup

// 管道方式存在的问题:
// 1. 使用全局变量在跨包调用时不容易实现规范和统一,需要维护一个共用的channel

func worker(exitChan chan struct{}) {
LOOP:
    for {
        fmt.Println("worker")
        time.Sleep(time.Second)
        select {
        case <-exitChan: // 等待接收上级通知
            break LOOP
        default:
        }
    }
    wg.Done()
}

func main() {
    var exitChan = make(chan struct{})
    wg.Add(1)
    go worker(exitChan)
    time.Sleep(time.Second * 3) // sleep3秒以免程序过快退出
    exitChan <- struct{}{}      // 给子goroutine发送退出信号
    close(exitChan)
    wg.Wait()
    fmt.Println("over")
}

The official version of the program

package main

import (
    "context"
    "fmt"
    "sync"

    "time"
)

var wg sync.WaitGroup

func worker(ctx context.Context) {
LOOP:
    for {
        fmt.Println("worker")
        time.Sleep(time.Second)
        select {
        case <-ctx.Done(): // 等待上级通知
            break LOOP
        default:
        }
    }
    wg.Done()
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    wg.Add(1)
    go worker(ctx)
    time.Sleep(time.Second * 3)
    cancel() // 通知子goroutine结束
    wg.Wait()
    fmt.Println("over")
}

When the sub goroutine had opened another goroutine, only you need to pass ctx:

package main

import (
    "context"
    "fmt"
    "sync"

    "time"
)

var wg sync.WaitGroup

func worker(ctx context.Context) {
    go worker2(ctx)
LOOP:
    for {
        fmt.Println("worker")
        time.Sleep(time.Second)
        select {
        case <-ctx.Done(): // 等待上级通知
            break LOOP
        default:
        }
    }
    wg.Done()
}

func worker2(ctx context.Context) {
LOOP:
    for {
        fmt.Println("worker2")
        time.Sleep(time.Second)
        select {
        case <-ctx.Done(): // 等待上级通知
            break LOOP
        default:
        }
    }
}
func main() {
    ctx, cancel := context.WithCancel(context.Background())
    wg.Add(1)
    go worker(ctx)
    time.Sleep(time.Second * 3)
    cancel() // 通知子goroutine结束
    wg.Wait()
    fmt.Println("over")
}

Context acquaintance

Go1.7 adding a new standard library context, which defines the Contexttype, designed to simplify the process for data requests between a plurality of individual goroutine domain request, the cancel signal, the deadline, and other related operations, which may involve multiple an API call.

Incoming requests to the server should create a context, and outgoing calls to the server should accept context. Function call chain context must be passed between them, or you can use WithCancel, WithDeadline, WithTimeoutor WithValuecreate derivative context. When a context is canceled, all derived context it was canceled.

Context interface

context.ContextIs an interface, the interface defines four methods need to be implemented. Specific signature as follows:

type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key interface{}) interface{}
}

among them:

  • DeadlineThe method needs to return the current Contexttime to be canceled, which is the deadline for the completion of the work (deadline);
  • DoneThe method requires a return Channel, the Channel will be closed after the current work is completed or canceled context, repeatedly invoked Donemethod returns the same Channel;
  • ErrThe method returns the current Contextcause of ending, it will only Donewill return a non-null value returned by Channel was closed;
    • If the current Contextis canceled it will return Canceledan error;
    • If the current Contexttime-out will return DeadlineExceededan error;
  • ValueThe method will Contextreturn the value corresponding to the key, for a same context, the multiple calls Valueand pass the same Keywill return the same result, only the method for communicating data across the API with the request field and inter-process;

Background()和TODO()

Go two built-in functions: Background()and TODO()these two functions return an implementation of the Contextinterface backgroundand todo. We are beginning to code two built-in context object as the topmost partent context, derived more sub-context object.

Background()Mainly used for the main function, initialization and test code, as the topmost Context Context of this tree, i.e. the root Context.

TODO()It is not yet know the specific usage scenarios, if we do not know when to use what Context, you can use this.

backgroundAnd todoare essentially emptyCtxstructure type, is a non-cancelable, deadline is not set, it does not carry any value Context.

With family of functions

In addition, contextthe package also defines a series of four With function.

WithCancel

WithCancelThe function signature is as follows:

func WithCancel(parent Context) (ctx Context, cancel CancelFunc)

WithCancelReturns a copy of the parent node Done with the new channel. When the call to return to cancel the function or when the context Done to close the parent channel, the channel Done Close to return to the context, no matter what occurs first.

Cancel this context will release its associated resources, so the operation should run the code in this context calls cancel immediately upon completion.

func gen(ctx context.Context) <-chan int {
        dst := make(chan int)
        n := 1
        go func() {
            for {
                select {
                case <-ctx.Done():
                    return // return结束该goroutine,防止泄露
                case dst <- n:
                    n++
                }
            }
        }()
        return dst
    }
func main() {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel() // 当我们取完需要的整数后调用cancel

    for n := range gen(ctx) {
        fmt.Println(n)
        if n == 5 {
            break
        }
    }
}

Sample code above, the genfunction generates and sends them to the integer in a separate return channel in goroutine. gen caller after use integer generated need to cancel context, so as not to genstart internal goroutine leak.

WithDeadline

WithDeadlineThe function signature is as follows:

func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)

Returns a copy of the parent context, and adjust the deadline later than d. If the deadline has earlier parent context in d, the WithDeadline (parent, d) semantically equivalent to the parent context. When the deadline expires, when the cancel function call returns, or when the parent context Done passage is closed, returns the context of the Done passage is closed, whichever occurs first.

Cancel this context will release its associated resources, so the operation should run the code in this context calls cancel immediately upon completion.

func main() {
    d := time.Now().Add(50 * time.Millisecond)
    ctx, cancel := context.WithDeadline(context.Background(), d)

    // 尽管ctx会过期,但在任何情况下调用它的cancel函数都是很好的实践。
    // 如果不这样做,可能会使上下文及其父类存活的时间超过必要的时间。
    defer cancel()

    select {
    case <-time.After(1 * time.Second):
        fmt.Println("overslept")
    case <-ctx.Done():
        fmt.Println(ctx.Err())
    }
}

The above code defines a 50 ms after the expiration of the deadline, then we call context.WithDeadline(context.Background(), d)to get a context (ctx) and a cancel function (cancel), and then use a select make the main program into a wait: Wait one second printing oversleptExit or wait ctx quit expired. Because ctx50 seconds after the expiration date, it ctx.Done()will first receives the value, the above code will print ctx.Err () The reason for cancellation.

WithTimeout

WithTimeoutThe function signature is as follows:

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

WithTimeoutReturn WithDeadline(parent, time.Now().Add(timeout)).

Cancel this context associated with the release of resources, and therefore, run code should in this context cancel call immediately after completion, typically for a timeout control database or network connection. Specific examples are as follows:

package main

import (
    "context"
    "fmt"
    "sync"

    "time"
)

// context.WithTimeout

var wg sync.WaitGroup

func worker(ctx context.Context) {
LOOP:
    for {
        fmt.Println("db connecting ...")
        time.Sleep(time.Millisecond * 10) // 假设正常连接数据库耗时10毫秒
        select {
        case <-ctx.Done(): // 50毫秒后自动调用
            break LOOP
        default:
        }
    }
    fmt.Println("worker done!")
    wg.Done()
}

func main() {
    // 设置一个50毫秒的超时
    ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*50)
    wg.Add(1)
    go worker(ctx)
    time.Sleep(time.Second * 5)
    cancel() // 通知子goroutine结束
    wg.Wait()
    fmt.Println("over")
}

WithValue

WithValueFunction can be used to request data scope relationship with Context object. The following statement:

func WithValue(parent Context, key, val interface{}) Context

WithValueReturns a copy of the parent node, which is associated with key val.

Data transfer between processes and only API request field using context values, rather than using it to pass optional arguments to the function.

Keys provided must be comparable, and should not be stringtype or any other built-in type, in order to avoid the use of a conflict occurs between the packet context. WithValueThe user should define their own key type. To avoid partitioned allocated to the interface {}, usually having a particular type of context key struct{}. Or, the static type derived context crucial variable should be a pointer or interface.

package main

import (
    "context"
    "fmt"
    "sync"

    "time"
)

// context.WithValue

type TraceCode string

var wg sync.WaitGroup

func worker(ctx context.Context) {
    key := TraceCode("TRACE_CODE")
    traceCode, ok := ctx.Value(key).(string) // 在子goroutine中获取trace code
    if !ok {
        fmt.Println("invalid trace code")
    }
LOOP:
    for {
        fmt.Printf("worker, trace code:%s\n", traceCode)
        time.Sleep(time.Millisecond * 10) // 假设正常连接数据库耗时10毫秒
        select {
        case <-ctx.Done(): // 50毫秒后自动调用
            break LOOP
        default:
        }
    }
    fmt.Println("worker done!")
    wg.Done()
}

func main() {
    // 设置一个50毫秒的超时
    ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*50)
    // 在系统的入口中设置trace code传递给后续启动的goroutine实现日志数据聚合
    ctx = context.WithValue(ctx, TraceCode("TRACE_CODE"), "12512312234")
    wg.Add(1)
    go worker(ctx)
    time.Sleep(time.Second * 5)
    cancel() // 通知子goroutine结束
    wg.Wait()
    fmt.Println("over")
}

Note the use of Context

  • Recommended to pass parameters displayed Context
  • Context function as a parameter to the method, should Context as the first parameter.
  • When passed to a function method Context, do not pass nil, if you do not know what passed, use context.TODO ()
  • Value Context related data necessary to deliver the requested domain method should not be used to pass optional parameters
  • Context is thread-safe, you can rest assured of passing the multiple goroutine

Client timeout cancel examples

How to control the timeout when the client calls the server-side API?

end server

// context_timeout/server/main.go
package main

import (
    "fmt"
    "math/rand"
    "net/http"

    "time"
)

// server端,随机出现慢响应

func indexHandler(w http.ResponseWriter, r *http.Request) {
    number := rand.Intn(2)
    if number == 0 {
        time.Sleep(time.Second * 10) // 耗时10秒的慢响应
        fmt.Fprintf(w, "slow response")
        return
    }
    fmt.Fprint(w, "quick response")
}

func main() {
    http.HandleFunc("/", indexHandler)
    err := http.ListenAndServe(":8000", nil)
    if err != nil {
        panic(err)
    }
}

client end

// context_timeout/client/main.go
package main

import (
    "context"
    "fmt"
    "io/ioutil"
    "net/http"
    "sync"
    "time"
)

// 客户端

type respData struct {
    resp *http.Response
    err  error
}

func doCall(ctx context.Context) {
    transport := http.Transport{
       // 请求频繁可定义全局的client对象并启用长链接
       // 请求不频繁使用短链接
       DisableKeepAlives: true,     }
    client := http.Client{
        Transport: &transport,
    }

    respChan := make(chan *respData, 1)
    req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
    if err != nil {
        fmt.Printf("new requestg failed, err:%v\n", err)
        return
    }
    req = req.WithContext(ctx) // 使用带超时的ctx创建一个新的client request
    var wg sync.WaitGroup
    wg.Add(1)
    defer wg.Wait()
    go func() {
        resp, err := client.Do(req)
        fmt.Printf("client.do resp:%v, err:%v\n", resp, err)
        rd := &respData{
            resp: resp,
            err:  err,
        }
        respChan <- rd
        wg.Done()
    }()

    select {
    case <-ctx.Done():
        //transport.CancelRequest(req)
        fmt.Println("call api timeout")
    case result := <-respChan:
        fmt.Println("call server api success")
        if result.err != nil {
            fmt.Printf("call server api failed, err:%v\n", result.err)
            return
        }
        defer result.resp.Body.Close()
        data, _ := ioutil.ReadAll(result.resp.Body)
        fmt.Printf("resp:%v\n", string(data))
    }
}

func main() {
    // 定义一个100毫秒的超时
    ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*100)
    defer cancel() // 调用cancel释放子goroutine资源
    doCall(ctx)
}

Guess you like

Origin www.cnblogs.com/nickchen121/p/11517434.html