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.
- Don’t put
Context
It’s plugged into the structure. Directly to theContext
The type is the first argument to a function and is usually namedctx
. - Do not pass one to the function
The context of nil
If you really don’t know what to upload, does the standard library have one for youContext: todo
. - Don’t stuff a type that should be a function parameter
context
,context
It should store some common data. For example: logged inThe session, cookie,And so on. - The same
context
It may be passed to more than onegoroutine
Don’t worry.context
It 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 onecontext
Node, that is, a parent node that is close to each othercontext