Context Background and Applicable Scenarios
Context background
Golang did not have its own context in 1.6.2, so in version 1.7//x/net/contextThe package was addedOfficial librarymiddle. Golang's Context package, which can be called "context" in Chinese, is used to pass context information between goroutine coroutines, including kv data, cancel signal, timeout time, deadline, etc.
Functions and Purposes of Context
Although we know the basic information of the context context, think about it, why do we screw the Context out separately in Go? This has a lot to do with the concurrency of Go, because it is very easy to create concurrent coroutines in Go. However, if there is no relevant mechanism to control the life cycle of these coroutines, it may lead to a flood of coroutines, and may also cause a large number of requests to time out, the coroutines cannot exit, and the coroutines leaks lead to the coroutines to be unable to release resources occupied by coroutines, which leads to various problems such as full resource. Therefore, the purpose of context appears is to solve the exit control of parent-child processes between concurrent coroutines.
A common example is that there is a web server that provides a request and opens multiple coroutines to process the business logic of the request, such as querying the login status, obtaining user information, obtaining business information, etc. If the life cycle of the requested downstream coroutine cannot be controlled, then our business request may always time out, and the business service may leak the coroutine because the coroutine is not released. Therefore, it is very important to be able to notify events between coroutines and control the life cycle of coroutines. How to implement it? context is here to do these things.
In addition, since there are a large number of concurrent coroutines, what should I do if I want to share some basic data between each coroutine, such as passing the tarceID of each requesting link, and stringing the entire link together? Still have to rely on context.
Overall, the purpose of context mainly includes two:
- Event notification between coroutines (timeout, cancellation)
- Data passing key-value pairs between coroutines (kv data)
Basic usage of Context
Use the official Context in Go language directly"context"
The package can be used. Generally, variables of type are passed in all places we want to pass (the first parameter of the function) and used related APIs. context Commonly used postures include but are not limited to:
- Data is passed through context, but only some general or basic metadata can be passed here. Do not pass business-level data. It is not that it cannot be passed, but it is not recommended in Go's encoding specifications or idioms.
- Timeout control of coroutines through context
- Concurrent control through context
Context's synchronous control design
There are two classic ways to control concurrency in Go, one is WaitGroup, and the other is Context.
In Go, when multiple batches of computing tasks need to be synchronized, or a one-to-many collaboration process is required; through the relationship between the Context (go's context is designed to include a parent-child relationship), we can control the life cycle of the child coroutine, while other synchronization methods cannot control their life cycle, and can only be passive blocking and waiting for completion or ending. context controls the life cycle of a child coroutine and is implemented through the context mechanism. This is a general application method for various frameworks and libraries in general systems or at the underlying level. context takes some control over concurrency, including Context Done cancellation, deadline cancellation, timeout cancellation, etc.
For example, if there is a network request Request, each Request needs to enable a goroutine to do some business logic, and these goroutines may enable other goroutines. Then in this way, we can track and control these goroutines through the Context.
Another practical example is that in the web server implemented by Go, each request will be opened with a goroutine to process. However, in our goroutine request logic, we need to continue to create goroutine to access other backend resources, such as databases, RPC services, etc. Since these goroutines are all processing the same request, if the request timed out or is cancelled, all goroutines should exit immediately and release the relevant resources. In this case, Context also requires the use of Context to cancel all goroutines for us.
Definition and implementation of Context
Context interface interface definition
In golang, interface is a very widely used structure that can accept any type. The context is defined through interface. The definition is very simple, with a total of 4 methods. This is also the design concept of Go. The interface is as simple and compact as possible, and rich functions are achieved through combinations.
Definition is as follows:
type Context interface { // Returns the deadline for whether context will be cancelled and automatically cancelled (i.e. deadline) Deadline() (deadline , ok bool) // When context is cancelled or reaches deadline, return a closed channel Done() <-chan struct{} // Return the error reason for cancellation, because the Context was cancelled Err() error // Get the value corresponding to the key Value(key interface{}) interface{} }
- Deadline returns the deadline for whether the context will be cancelled and automatically canceled. The first return value is the deadline. At this time point, Context will automatically initiate a cancellation request; the second return value ok==false means that no deadline is set. If cancellation is needed, the cancel function needs to be called to cancel.
- The Done method returns a read-only chan with type struct{}. If the chan returned by this method can be read, it means that the parent context has initiated a cancellation request. When we receive this signal through the Done method, we should do a cleanup operation, then exit the goroutine and release the resource. After that, the Err method returns an error telling why the Context was cancelled.
- The Err method returns the error reason for cancellation because of why Context was cancelled.
- The Value method obtains the key-value pair saved on the Context, so you need to use a Key to get the corresponding value. This value is generally thread-safe (concurrency-safe).Although context is a concurrently safe type, if values are stored in context, these values are usually not concurrently safe. Concurrent reading and writing these values may cause data confusion, and panic may occur in severe cases. Therefore, during concurrency, if our business code needs to read and write value in context, it is best to recommend that we close a copy of the original context value and stuff it into a new ctx to pass it to each gorouinte. Of course, if there is no concurrent reading already, it can be used directly, or locked when using it.
The specific implementation of parent Context
Although Context is an interface, it does not require the user to implement it. The built-in context package of golang has been implemented for us. If you look at the source code of Go, you can see the following definition:
var ( background = new(emptyCtx) todo = new(emptyCtx) ) func Background() Context { return background } func TODO() Context { return todo }
Background and TODO are actually implemented based on emptyCtx. The emptyCtx type implements 4 methods defined by the context interface. It itself is a Context that cannot be cancelled, has no deadline set, and does not carry any value. Check the official source code as follows:
type emptyCtx int func (*emptyCtx) Deadline() (deadline , ok bool) { return } func (*emptyCtx) Done() <-chan struct{} { return nil } func (*emptyCtx) Err() error { return nil } func (*emptyCtx) Value(key interface{}) interface{} { return nil }
The Background method is generally defined and used at the entrance of the main function (or the initial root context requested), and then passed down. All subcoroutines are derived based on the main context. TODO is generally not recommended for business use, and it is generally meaningless and can be used in unit tests.
Context inheritance and various With series functions
View official documentation//x/net/context
// The most basic implementation can also be called the parent contextfunc Background() Context func TODO() Context // Various With series functions derived from Background() root contextfunc WithCancel(parent Context) (ctx Context, cancel CancelFunc) func WithDeadline(parent Context, deadline ) (Context, CancelFunc) func WithTimeout(parent Context, timeout ) (Context, CancelFunc) func WithValue(parent Context, key interface{}, val interface{}) Context
- WithCancel function, passing a parent Context as a parameter, returning a child Context, and a cancel function to cancel the Context. We mentioned earlier that we control the life cycle of parent-child coroutines, so we can implement it through this function
- The WithDeadline function is similar to WithCancel, but it will pass an extra cutoff time parameter. In this way, when the deadline is reached, the Context will be automatically cancelled. Of course, we can also cancel it without waiting for this time, and then cancel it in advance by canceling the function.
- The WithTimeout function, like WithDeadline, will pass in a timeout timeout, that is, from now on, until the timeout time is over, the timeout will be canceled. Note that this is a timeout cancellation, not a deadline cancellation.
- WithValue function, this has nothing to do with WithCancel. It is not used to control the life cycle of the parent-child coroutine. This is what we mentioned. It is used to pass basic metadata in the context. This can store the data of key-value pairs in the context, and then the data of this key-value pair can be obtained through the method. This is a technique we often use in actual use. Generally, when we want to pass data through the context, we can use this method, such as when we need tarceID to track the system call stack.
Common method examples of Context
1. Call the Context Done method to cancel
func ContextDone(ctx , out chan<- Value) error { for { v, err := AllenHandler(ctx) if err != nil { return err } select { case <-(): ("context has done") return () case out <- v: } } }
2. Pass the value through
func main() { ctx, cancel := (()) valueCtx := (ctx, key, "add value from allen") go watchAndGetValue(valueCtx) (10 * ) cancel() (5 * ) } func watchAndGetValue(ctx ) { for { select { case <-(): //get value ((key), "is cancel") return default: //get value ((key), "int goroutine") (2 * ) } } }
3. Timeout Cancel
package main import ( "fmt" "sync" "time" "/x/net/context" ) var ( wg ) func work(ctx ) error { defer () for i := 0; i < 1000; i++ { select { case <-(2 * ): ("Doing some work ", i) // we received the signal of cancelation in this channel case <-(): ("Cancel the context ", i) return () } } return nil } func main() { ctx, cancel := ((), 4*) defer cancel() ("Hey, I'm going to do some work") (1) go work(ctx) () ("Finished. I'm going home") }
4. Deadline canceled
package main import ( "context" "fmt" "time" ) func main() { d := ().Add(1 * ) ctx, cancel := ((), d) // Even though ctx will be expired, it is good practice to call its // cancelation function in any case. Failure to do so may keep the // context and its parent alive longer than necessary. defer cancel() select { case <-(2 * ): ("oversleep") case <-(): (()) } }
Context usage principles and tips
- Context is thread-safe and can be passed in multiple goroutine coroutines with confidence.
- A Context object can be passed to any number of gorotuines. When performing a cancel operation on it, all goroutines will receive a cancel signal.
- Don't put Context in a structure, pass it in the form of parameters. Parent Context is generally Background, and it should be created at the entrance of the main function and passed it down.
- The variable names of Context are recommended to be uniformly ctx, and the Context should be passed as the first parameter to each function on the ingress request and exit request link.
- When passing a Context to a function method downstream, do not pass nil, otherwise the link will be interrupted when tarce tracking, and if there is logic to get values in the function, it may lead to panic.
- The Value of Context can only pass some general or basic metadata. It does not mean that it cannot be passed on. It is not recommended to use this data in Go's encoding specifications or idioms. Since the context storage key-value is chained, the query complexity is O(n), so try not to store unnecessary data at will
This is the end of this article about detailed explanation of the principles and usage techniques of Context in Golang. For more related Golang Context content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!