The background,
One of the main concepts in Golang is the concurrent coroutine Goroutine, which can start a coroutine and run it with a single keyword, go.
A single Goroutine running is fine. If you have a goroutine that’s derived from multiple Goroutines, and they need to interact with each other — like transferring data — how do you transfer data to each other? If a child goroutine is cancelled, how can I cancel the related goroutine?
Such as: In the Go Web server, each request is made in a separate Goroutine. These goroutines may open other goroutines for other operations. How to transfer data between goroutines and how to cancel goroutine when encountering problems?
Sometimes in program development, one goroutine is used for each request. However, other Goroutines are often required to access back-end data resources, such as databases, RPC services, etc. These Goroutines are all processing the same request, so they need to access some shared resources. If a request times out or is cancelled, all goroutines associated with this request should exit and release resources, such as user identity information, authentication token, etc.
Since Golang doesn’t have a Goroutine ID like the THREAD ID in C, you can’t close goroutine directly with the ID. But there are other ways.
Solutions:
- Use time to indicate expiration or timeout
- Signal a request that it is time to stop
- End the request with channel notification
To do this, Golang provides us with a simple action package: the Context package.
What is Context
Context package golang Context package golang Context package golang Context package golang Context package golang Context package golang Context package golang Context package golang Context package golang Context package golang Context package golang Context package golang Context package golang Context package golang Context package golang Context package golang Context package
Context function
- 3.1 Control goroutine exit
- Exit WithCancel in time
- Time point exit WithDeadline
- Time interval exit WithTimeout
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
Copy the code
WithCancel, bound to a parent, returns a cancelCtx Context, and CancelFunc is used to actively close the Context. Once cancel is called, the created Context is cancelled.
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
Copy the code
WithDeadline, the cancelCtx Context with an expiration date, i.e. the CancelFunc method will not be called until the specified point in time
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
Copy the code
WithTimeout, cancelCtx Context with a timeout, which is a wrapper with WithDeadline, but WithTimeout is the interval and Deadline is the point in time.
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
}
Copy the code
- 3.2 set values
func WithValue(parent Context, key, val interface{})
Copy the code
4. Source code analysis
Go version go1.13.9
4.1 Overall program analysis
/src/context/context.go
-
Important interface
- Context: Defines four methods of the Context interface.
- Canceler: Cancels the context interface and defines two methods.
-
Important structure
- EmptyCtx: implements the Context interface, it is an empty Context, it can never be cancelled, has no value, has no deadline. Its main functions
context.Background()
andcontext.TODO()
Return this root context or context that doesn’t do anything. In parent-child terms, emptyCtx is used to create the parent context. - CancelCtx: can be cancelled
- TimerCtx: Timeout will be cancelled
- ValueCtx: Stores k-V key data
- EmptyCtx: implements the Context interface, it is an empty Context, it can never be cancelled, has no value, has no deadline. Its main functions
-
The important function
- Backgroud: Returns an empty context, often used as the root context
- TODO: Returns an empty context, as in refactoring when no suitable context is available
- NewCancenCtx: Creates a cancelable context
- ParentCancelCtx: Finds the first cancelable parent node
- WithCancel: Generates a cancelable context based on the parent contxt
- WithDeadline: Creates a context with a deadline
- WithTimeout: Creates a context with an expiration time
- WithValue: Creates a context to store key-value pairs K -v
What’s the difference between Background and TODO?
There are not many differences between the two functions, but there are some differences in usage and semantics:
- Background: is the context default from which all other contexts should derive
- TODO: Only used when you are not sure which context to use
4.2 the Context interface
type Context interface {
// Deadline returns an expired timer and whether the current one is expired
Deadline() (deadline time.Time, ok bool)
// Done returns a closed channel after the current context is complete, indicating that the current context should be cancelled for Goroutine to clean up
// WithCancel: Is responsible for closing Done when Cancel is called
// WithDeadline: Responsible for closing Done when its deadline expires
// WithTimeout: Closes done after timeout
Done() <-chan struct{}
// Return nil if the Done channel is not closed
// Otherwise a concrete error is returned
Canceled
/ / DeadlineExceeded expired
Err() error
// Return the value of the corresponding key
Value(key interface{}) interface{}}Copy the code
- Done () :
Returns a channel, which can signal that the context has been cancelled. Returns a closed channel when a channel is closed or when a deadline is reached. This is a read-only channel. Reading a closed channel will read the corresponding zero value. And there’s no place in the source code to stuff a value into this channel, so reading this channel in a subcoroutine won’t read anything unless it’s closed. This allows the subcoroutine to do some cleanup and exit as soon as it reads a value (zero) from a channel.
- Deadline () :
The Context is used to set the timeout, and its return value (which returns the timeout set by the parent task) is used to indicate the point at which the Context was cancelled, and by which you can determine the next operation. For example, if you time out, you can cancel the operation.
- The Value (s) :
Gets the value of the key pair set earlier
- Err () :
Returns an error indicating the reason the channel was closed. Like canceled or timed out
4.3 emptyCtx structure
EmptyCtx is an implementation of a context that does not cancel, has no expiration date, has no value, and does not return an error. It returns the root context as context.background () and context.todo () or does nothing to the context. In parent-child terms, emptyCtx is used to create the parent context.
type emptyCtx int
func (*emptyCtx) Deadline(a) (deadline time.Time, ok bool) {
return
}
func (*emptyCtx) Done(a) <-chan struct{} {
return nil
}
func (*emptyCtx) Err(a) error {
return nil
}
func (*emptyCtx) Value(key interface{}) interface{} {
return nil
}
func (e *emptyCtx) String(a) string {
switch e {
case background:
return "context.Background"
case todo:
return "context.TODO"
}
return "unknown empty Context"
}
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)
Copy the code
4.4 cancelCtx structure
CancelCtx struct:
type cancelCtx struct {
Context
mu sync.Mutex // protects following fields
done chan struct{} // created lazily, closed by first cancel call
children map[canceler]struct{} // set to nil by the first cancel call
err error // set to non-nil by the first cancel call
}
Copy the code
- Context: cancelCtx inserts a Context interface object as an anonymous field. This Context is the parent Context
- Mu: indicates the protected field
- Children: Internally holds all interfaces of the canceler context that can be cancelled. Later, if the current context is cancelled, only the context of all canceler interfaces can be cancelled
- “Done” : indicates cancellation
- Err: Error information
Done () function:
func (c *cancelCtx) Done(a) <-chan struct{} {
c.mu.Lock()
if c.done == nil {
c.done = make(chan struct{})
}
d := c.done
c.mu.Unlock()
return d
}
Copy the code
The function returns a read-only channel that has no data written to it. So calling this read-only channel directly will block. It is usually used with select. Once off, the zero value is immediately read.
The cancel () function:
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
if err == nil {// We must pass an err value
panic("context: internal error: missing cancel error")
}
c.mu.Lock()
ifc.err ! =nil {
c.mu.Unlock()
return Canceled already canceled by another coroutine
}
c.err = err
// Close channel to inform other coroutines
if c.done == nil {
c.done = closedchan
} else {
close(c.done)
}
// Traversing it is all child nodes
for child := range c.children {
// NOTE: acquiring the child's lock while holding parent's lock.
child.cancel(false, err)// Recursively cancel all child nodes
}
// Clear the child node
c.children = nil
c.mu.Unlock()
if removeFromParent {
// Remove yourself from the parent node
removeChild(c.Context, c)
}
}
Copy the code
This function closes channel: c.tone (); Recursively cancels all of its children; Finally, delete yourself from the parent node. By closing a channel, the cancellation signal is passed to all of its children. The goroutine receives the cancel signal when read C. tone in the SELECT statement is selected
4.5 timerCtx structure
TimerCtx struct:
type timerCtx struct {
cancelCtx
timer *time.Timer // Under cancelCtx.mu.
deadline time.Time
}
Copy the code
TimerCtx is embedded in the cancelCtx structure, so the cancelCtx method can also be used. TimerCtx is mainly used to implement two context implementations WithDeadline and WithTimeout, which inherits the cancelCtx structure, and also contains a timer.Timer timer and a Deadline termination implementation. The Timer automatically cancels the context when the deadline arrives.
The cancel () function:
func (c *timerCtx) cancel(removeFromParent bool, err error) {
c.cancelCtx.cancel(false, err) CancelCtx cancelCtx cancelCtx cancelCtx cancelCtx cancel(
if removeFromParent {
// Remove this timerCtx from its parent cancelCtx's children.
removeChild(c.cancelCtx.Context, c)
}
c.mu.Lock()
ifc.timer ! =nil {
c.timer.Stop()// Stop the timer
c.timer = nil
}
c.mu.Unlock()
}
Copy the code
This function inherits cancelCtx cancel(), followed by its own timer Stop() to cancel.
4.6 valueCtx Structure
type valueCtx struct {
Context
key, val interface{}}Copy the code
Value is saved by key-value
func (c *valueCtx) String(a) string {
return contextName(c.Context) + ".WithValue(type " +
reflectlite.TypeOf(c.key).String() +
", val " + stringify(c.val) + ")"
}
func (c *valueCtx) Value(key interface{}) interface{} {
if c.key == key {
return c.val
}
return c.Context.Value(key)
}
Copy the code
4.7 WithCancel method
WithCancel:
Create a cancelable context
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
c := newCancelCtx(parent)
propagateCancel(parent, &c)
return &c, func(a) { c.cancel(true, Canceled) }
}
Copy the code
Passing in a parent context (usually a background as the root node) returns a new context. When CancelFunc returned by WithCancel is called or the parent’s Done Channel is closed (CancelFunc of the parent is called), the context’s done Channel is also closed.
NewCancelCtx () method
func newCancelCtx(parent Context) cancelCtx {
return cancelCtx{Context: parent}
}
Copy the code
Initializes the cancelCtx structure
PropagateCancel () method
This function cancels the child context when the parent context is cancelled. There are two modes:
- Notify the child to cancel when the parent cancels
2. Call the hierarchical recursive cancellation of child when parent cancels
// propagateCancel arranges for child to be canceled when parent is.
func propagateCancel(parent Context, child canceler) {
// The parent node is empty and returns directly
if parent.Done() == nil {
return // parent is never canceled
}
if p, ok := parentCancelCtx(parent); ok {
p.mu.Lock()
ifp.err ! =nil {
// parent has already been canceled
child.cancel(false, p.err)// The parent node has been cancelled and its children need to be cancelled as well
} else {
// The parent node is not cancelled
if p.children == nil {
p.children = make(map[canceler]struct{})}// Place the child on the parent node
p.children[child] = struct{}{}
}
p.mu.Unlock()
} else {
// If no parent context can be cancelled is found. Start a new coroutine to monitor the cancellation signal of the parent node or child node
go func(a) {
select {
// Ensure that the child node is cancelled when the parent node is cancelled
case <-parent.Done():
child.cancel(false, parent.Err())
case <-child.Done():
}
}()
}
}
Copy the code
parentCancelCtx
This function recognizes three types of Context: cancelCtx, timerCtx, valueCtx
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
for {
switch c := parent.(type) {
case *cancelCtx:
return c, true // Find the parent that recently supported cancel, and the parent calls the cancel operation
case *timerCtx:
return &c.cancelCtx, true // Find the parent that recently supported cancel, and the parent calls the cancel operation
case *valueCtx:
parent = c.Context / / recursion
default:
return nil.false}}}Copy the code
4.8 Function cancelled by time
- WithTimeout
- WithDeadline
WithTimeout is a direct call to the WithDeadline function, and the passed deadline is the current time +timeout time, that is, from now on, the timeout time will be counted as timeout. In other words, WithDeadline uses absolute time.
WithTimeout():
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
}
Copy the code
WithDeadline()
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
if cur, ok := parent.Deadline(); ok && cur.Before(d) {
// The current deadline is already sooner than the new one.
return WithCancel(parent)
}
c := &timerCtx{
cancelCtx: newCancelCtx(parent),
deadline: d,
}
// Listen for parent's cancellation, or register itself with parent
propagateCancel(parent, c)
dur := time.Until(d)
if dur <= 0 {
// It has expired
c.cancel(true, DeadlineExceeded) // deadline has already passed
return c, func(a) { c.cancel(false, Canceled) }
}
c.mu.Lock()
defer c.mu.Unlock()
if c.err == nil {
c.timer = time.AfterFunc(dur, func(a) {
c.cancel(true, DeadlineExceeded)
})
}
return c, func(a) { c.cancel(true, Canceled) }
}
Copy the code
In the process of creating timerCtx, the WithDeadline method determines the expiration date and current date of the parent context, and creates a timer through time.AfterFunc. When the expiration date is exceeded, the timerctx. cancel method will be called to synchronize the cancellation signal.
WithCancel, WithDeadline, and WithTimeout all return a Context and a CancelFunc function. The Context returned is the cancelCtx or timerCtx that we currently created based on parent, CancelFunc cancels the current Context, even if timerCtx has not timed out.
4.9 WithValue ()
www.cnblogs.com/qcrao-2018/…
// Create a function for valueCtx
func WithValue(parent Context, key, val interface{}) Context {
if key == nil {
panic("nil key")}if! reflectlite.TypeOf(key).Comparable() {panic("key is not comparable")}return &valueCtx{parent, key, val}
}
func (c *valueCtx) String(a) string {
return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val)
}
Copy the code
The key is required to be comparable, because then you need to fetch the value in the context with the key, and that’s necessary. By passing the context around, you end up with a tree like this.
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. The process of evaluating, in fact, is a recursive search process:
func (c *valueCtx) Value(key interface{}) interface{} {
if c.key == key {
return c.val
}
return c.Context.Value(key)
}
Copy the code
It looks up the link, compares whether the current node’s key is the desired key, and returns value if it is. Otherwise, follow the context all the way to the root node (usually emptyCtx) and return nil. So when you use the Value method you have to determine if the result is nil. Because the search direction is upward, the parent node cannot get the value stored by the child node, but the child node can get the value of the parent node. The process of creating the context node WithValue is actually the process of creating the linked list node
Another of my blog: www.cnblogs.com/jiujuan/p/1…
reference
- www.cnblogs.com/qcrao-2018/…
- blog.golang.org/context
- Draveness. Me/golang/docs…
- Studygolang.com/articles/13…