[Golang] Golang Advanced Series Tutorials--What can Go language context do?

foreword

In the source code of many Go projects, you will find a very common parameter ctx in the process of reading, and it is basically used as the first parameter of the function.

Why do you write like this? What is the use of this parameter? With such doubts, I studied the story behind this parameter.
A picture to start:

The core is the Context interface:

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

Contains four methods:

  • Done(): Returns a channel when the times out or the cancel method is called.
  • Err(): Returns an error indicating the reason for canceling ctx.
  • Deadline(): returns the deadline and a bool value.
  • Value(): Returns the value corresponding to the key.

There are four structures that implement this interface, namely: emptyCtx, cancelCtx, timerCtx and valueCtx.

Among them, emptyCtx is an empty type, which exposes two methods:

func Background() Context
func TODO() Context

Under normal circumstances, use Background() as the root ctx, and then derive child ctx based on it. Use TODO() if you are not sure which ctx to use.
The other three also expose corresponding methods:

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

follow the rules

When using Context, follow the following four rules:

  • Don't put the Context into a structure, but pass it in as the first parameter, named ctx.
  • Do not pass in a nil Context, even if the function allows it. If you don't know which Context to use, you can use context.TODO().
  • Value-related methods using Context should only be used to pass metadata related to requests in programs and interfaces, and do not use it to pass some optional parameters.
  • The same Context can be passed to different goroutines; Context is safe for concurrency.

WithCancel

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

WithCancel returns a copy of the parent with a new Done channel. The Done channel of the returned ctx will be closed when the returned cancel function is called or the parent context's Done channel is closed.

Canceling this context releases the resources associated with it, so code should call cancel as soon as operations running in this context complete.

As an example:
this code demonstrates how to use a cancelable context to prevent goroutine leaks. At the end of the function, the goroutine started by gen will return without leaking.

package main

import (
    "context"
    "fmt"
)

func main() {
    
    
    // gen generates integers in a separate goroutine and
    // sends them to the returned channel.
    // The callers of gen need to cancel the context once
    // they are done consuming generated integers not to leak
    // the internal goroutine started by gen.
    gen := func(ctx context.Context) <-chan int {
    
    
        dst := make(chan int)
        n := 1
        go func() {
    
    
            for {
    
    
                select {
    
    
                case <-ctx.Done():
                    return // returning not to leak the goroutine
                case dst <- n:
                    n++
                }
            }
        }()
        return dst
    }

    ctx, cancel := context.WithCancel(context.Background())
    defer cancel() // cancel when we are finished consuming integers

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

output:

2
3
4
5

WithDeadline

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

WithDeadline returns a copy of the parent context with the deadline adjusted to be no later than d. WithDeadline(parent, d) is semantically equivalent to parent if the parent's deadline is already earlier than d.

The returned context's Done channel is closed when the deadline expires, when the returned cancel function is called, or when the parent context's Done channel is closed.

Canceling this context releases the resources associated with it, so code should call cancel as soon as operations running in this context complete.

For example:
this code passes a context with a deadline to tell the blocking function that it should exit immediately when the deadline is reached.

package main

import (
    "context"
    "fmt"
    "time"
)

const shortDuration = 1 * time.Millisecond

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

    // Even though ctx will be expired, it is good practice to call its
    // cancellation function in any case. Failure to do so may keep the
    // context and its parent alive longer than necessary.
    defer cancel()

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

output:

context deadline exceeded

WithTimeout

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

WithTimeout returns WithDeadline(parent, time. Now(). Add(timeout)).
Canceling this context releases the resources associated with it, so code should call cancel as soon as operations running in this context complete.
As an example:
this code passes the context with a timeout to tell the blocking function that it should exit after the timeout.

package main

import (
    "context"
    "fmt"
    "time"
)

const shortDuration = 1 * time.Millisecond

func main() {
    
    
    // Pass a context with a timeout to tell a blocking function that it
    // should abandon its work after the timeout elapses.
    ctx, cancel := context.WithTimeout(context.Background(), shortDuration)
    defer cancel()

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

}

output:

context deadline exceeded

WithValue

func WithValue(parent Context, key, val any) Context

WithValue returns a copy of the parent with the value associated with key being val.
where keys must be comparable and should not be of type string or any other built-in type to avoid conflicts between packages using the context. Users of WithValue should define their own key types.

To avoid assigning to interface{}, context keys are usually of concrete struct{} type. Alternatively, the static type of the exported context key variable should be pointer or interface.

As an example:
this code demonstrates how to pass a value to the context and how to retrieve it (if it exists).

package main

import (
    "context"
    "fmt"
)

func main() {
    
    
    type favContextKey string

    f := func(ctx context.Context, k favContextKey) {
    
    
        if v := ctx.Value(k); v != nil {
    
    
            fmt.Println("found value:", v)
            return
        }
        fmt.Println("key not found:", k)
    }

    k := favContextKey("language")
    ctx := context.WithValue(context.Background(), k, "Go")

    f(ctx, k)
    f(ctx, favContextKey("color"))
}

output:

ound value: Go
key not found: color

Most of the content in this article, including code examples, are translated from official documents, and the codes are verified and executable. If there is something that is not particularly clear, you can read the official documentation directly.

Guess you like

Origin blog.csdn.net/u011397981/article/details/132043839