What does context do

Context is used to pass context information between goroutines, including: cancel signal, timeout, expiration time, K-V, etc.

Go is often used to write background services. It usually takes only a few lines of code to set up an HTTP server.

In a Go server, several Goroutines are usually started for each request: some Go to the database to fetch data, some call downstream interfaces to fetch relevant data…

These Goroutines need to share basic data about the request, such as the token for logging in, the maximum timeout for processing the request (if the request is returned after this value, the requester will not receive the data due to timeout), and so on. When a request is cancelled or takes too long to process, either because the user has closed the browser or because the requestor’s timeout has been exceeded, the requestor simply abandons the request. At this point, all goroutines working on this request need to exit quickly because their “work product” is no longer needed. After the associated Goroutine exits, the system can reclaim the associated resource.

In Go, we can’t kill coroutines directly. Coroutine closing is usually controlled by channel+ SELECT. But in some cases, such as processing a request that spawned many coroutines, these coroutines are related to each other: they need to share some global variables, have a common deadline, etc., and can be closed at the same time. Channel +select is a little bit more cumbersome, so you can use context.

Context is used to exit notification between goroutines, metadata transfer functions.

Context is very convenient to use. Context: context: context: context: context: context: context: context

func Background(a) Context
Copy the code

Background is an empty context that cannot be cancelled, has no value, and has no timeout. With the root node context, we provide four functions to create the child node context:

func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key, val interface{}) Context
Copy the code

Context is passed between function passes. You just call the cancel function at the appropriate time to signal cancellation to goroutines or call the Value function to fetch the Value from the context.

  1. Don’t putContextIt’s plugged into the structure. Directly to theContextThe type is the first argument to a function and is usually namedctx.
  2. Do not pass one to the functionThe context of nilIf you really don’t know what to upload, does the standard library have one for youContext: todo.
  3. Don’t stuff a type that should be a function parametercontext,contextIt should store some common data. For example: logged inThe session, cookie,And so on.
  4. The samecontextIt may be passed to more than onegoroutineDon’t worry.contextIt is concurrency safe.

Pass shared data

For Web server development, it is common to want to string together the entire process of a request. This relies heavily on variables of Thread Local (understood as unique to a single coroutine for Go). This is not a concept in the Go language, so you need to pass the context on function calls.

package main

import (
    "context"
    "fmt"
)
func main(a) {
    ctx := context.Background()
    process(ctx)
    ctx = context.WithValue(ctx, "traceId"."qcrao-2019")
    process(ctx)
}
func process(ctx context.Context) {
    traceId, ok := ctx.Value("traceId"). (string)
    if ok {
        fmt.Printf("process over. trace_id=%s\n", traceId)
    } else {
        fmt.Printf("process over. no trace_id\n")}}Copy the code

Running results:

process over. no trace_id
process over. trace_id=qcrao- 2019.
Copy the code

The first time the process function is called, CTX is an empty context and the traceId cannot be retrieved. The second time, we create a context with the WithValue function and assign the traceId key to it, so we can retrieve the value that was passed in.

Cancel the goroutine

Let’s imagine a scenario: open the order page for takeout, and the location of the delivery man is displayed on the map, which is updated every second. After the APP end initiates a Websocket connection request (possibly polling in reality) to the background, the background starts a coroutine, calculates the location of the little brother once every second, and sends it to the end. If the user exits this page, the background needs to cancel this process, exit Goroutine, and the system reclaims resources.

func Perform(a) {
    for {
        calculatePos()
        sendResult()
        time.Sleep(time.Second)
    }
}
Copy the code

If you need to “cancel” and don’t know the context function, you might add a pointer bool to the function, check at the beginning of the for statement if the bool variable changes from true to false, and exit the loop if it changes.

The simple method given above can achieve the desired effect, without problems, but it is not elegant, and can be troublesome once the coroutines are numerous and nested in various ways. The elegant way, of course, is to use the context.

func Perform(ctx context.Context) {
    for {
        calculatePos()
        sendResult()
        select {
        case <-ctx.Done():
            // Cancel, return directly
            return
        case <-time.After(time.Second):
            // block 1 second}}}Copy the code

The main process might look like this:

ctx, cancel := context.WithTimeout(context.Background(), time.Hour)
go Perform(ctx)
/ /...
// App returns to the page and calls the cancel function
cancel()
Copy the code

Notice one detail: The WithTimeout function returns a separate context from cancelFun. Context itself does not cancel the function. The reason for this is that the cancellation function can only be called by the outer function, preventing the child context from calling the cancellation function and thus strictly controlling the flow of information from the parent context to the child context.

Prevent goroutine leaks

In the previous example, goroutine would still run out and return, possibly wasting more system resources. Here’s an example of goroutine leaking if you don’t cancel the context

func gen(a) <-chan int {
    ch := make(chan int)
    go func(a) {
        var n int
        for {
            ch <- n
            n++
            time.Sleep(time.Second)
        }
    }()
    return ch
}
Copy the code

This is a coroutine that produces infinite integers, but if I only need the first five numbers it produces, then I get a Goroutine leak:

func main(a) {
    for n := range gen() {
        fmt.Println(n)
        if n == 5 {
            break}}/ /...
}
Copy the code

When n == 5, just break off. The coroutine of the gen function then executes an infinite loop, never stopping. There was a Goroutine leak.

Improve this example with context:

func gen(ctx context.Context) <-chan int {
    ch := make(chan int)
    go func(a) {
        var n int
        for {
            select {
            case <-ctx.Done():
                return
            case ch <- n:
                n++
                time.Sleep(time.Second)
            }
        }
    }()
    return ch
}
func main(a) {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel() // Avoid forgetting cancel elsewhere, and repeated calls do not affect
    for n := range gen(ctx) {
        fmt.Println(n)
        if n == 5 {
            cancel()
            break}}/ /...
}
Copy the code

Add a context and cancel the goroutine by calling the cancel function before break. After receiving the cancel signal, the gen function exits directly, and the system reclaims resources.

What is the search process for context.value

It’s a bit like a linked list, except in reverse: the Context points to its parent, and the linked list points to the next node. With the WithValue function, you can create layers of valueCtx that store variables that can be shared between goroutines.

When you look it up, it looks up to the last mounted onecontextNode, that is, a parent node that is close to each othercontext