Make writing a habit together! This is the second day of my participation in the “Gold Digging Day New Plan · April More text challenge”. Click here for more details. The context epsilon (┬ ┬ man ┬ ┬) 3
Context
In the Go service, a separate Goroutine is usually used to handle a request, but in this goroutine, other Goroutines may be opened to perform specific transactions, such as database, RPC, etc. At the same time, This group of Goroutines may also need to share access to special values, such as user tokens, request expiration time, etc. When a request times out, we want all goroutines associated with the request to exit quickly to reclaim system resources.
The Context package, which is open sourced by Google and added to the standard library in Go 1.7, makes it easy to pass specific values, cancel signals, and expiration dates to all goroutines involved in the request.
The core of the context package is the Context interface, which is structured as follows:
type Context interface {
Done() <-chan struct{}
Err() error
Deadline() (deadline time.Time, ok bool)
Value(key interface{}) interface{}}Copy the code
Done
Returns achan
Represents a cancel signal. When the channel is closed, the function should immediately finish and return.Err()
Returns aerror
, indicating the reason for removing the contextDeadline
Returns the time when the context was cancelledValue
Used to get from the contextkey
The value of the corresponding
use
Cancelation signals
As with chan to control concurrency, we want to pass a signal to the Goroutine that, once received, will immediately stop working and return. The context package provides a WithCancel(), which makes it easy to pass the cancel signal.
func useContext(ctx context.Context, id int) {
for {
select {
case <- ctx.Done():
fmt.Println("stop", id)
return
default:
run(id)
}
}
}
func G2(ctx context.Context) {
nCtx, nStop := context.WithCancel(ctx)
go G4(nCtx)
for {
select {
case <- ctx.Done():
fmt.Println("stop 2")
nStop()
return
default:
run(2)}}}func G3(ctx context.Context) {
useContext(ctx, 3)}func G4(ctx context.Context) {
useContext(ctx, 4)}func main(a) {
ctx, done := context.WithCancel(context.Background())
go G2(ctx)
go G3(ctx)
time.Sleep(5*time.Second)
done()
time.Sleep(5*time.Second)
}
Copy the code
Set a deadline
func G6(ctx context.Context) {
for {
select {
case <- ctx.Done():
t, _ := ctx.Deadline()
fmt.Printf("[*] %v done: %v\n", t, ctx.Err())
return
default:
fmt.Println("[#] run ...")}}}func main(a) {
// ctx, done := context.WithTimeout(context.Background(), time.Second * 2)
ctx, _ := context.WithTimeout(context.Background(), time.Second * 2)
go G6(ctx)
//done()
time.Sleep(10*time.Second) } [#] run ... . [*]2020- 10- 31 20:24:42.0581352 +0800 CST m=+2.008975001 done: context deadline exceeded
Copy the code
The value of
func G7(ctx context.Context) {
for {
select {
case <- ctx.Done():
fmt.Println("cancel", ctx.Value("key"))
return
default:
fmt.Println("running ", ctx.Value("key"))
time.Sleep(time.Second)
}
}
}
func main(a) {
ctx, _ := context.WithTimeout(context.Background(), time.Second * 2)
ctx = context2.WithValue(ctx, "key"."value")
go G7(ctx)
time.Sleep(10*time.Second)
}
Copy the code
The context package overview
The core of the context package is the context. context interface, and there are four structs that implement the context interface. EmptyCtx cancelCtx timerCtx valueCtx emptyCtx cancelCtx timerCtx valueCtx emptyCtx cancelCtx timerCtx valueCtx The context package exposes two methods, Background() and TODO(), to create an empty struct, and a total of four methods to generate an emptyCtx for each of the following structs: WithCancel(), WithDeadLine(), WithTimeout(), WithValue(), the corresponding relationship is as follows:
TODO and Background
The TODO and Background methods are used to return an emptyCtx type. They are implementatively the same:
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)
func Background(a) Context {
return background
}
func TODO(a) Context {
return todo
}
Copy the code
Both methods return a non-empty context emptyCtx, which is never cancelled and is passed to other methods to build more complex context objects. Background() is generally used by default, and TODO() is used only when in doubt, but in reality they are just different names.
Here is an implementation of emptyCtx, which does nothing.
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
}
Copy the code
WithCancel
type cancelCtx struct {
Context
mu sync.Mutex // For synchronization
done chan struct{} // Will be returned in Done
children map[canceler]struct{} // List of subcontexts. When done is closed, the map is traversed and all subcontexts are closed
err error // Turn off the exception generated by chan, which is not null when initialized
}
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
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
When WithCancel is called, a new cancelCtx is first copied from parent:
func newCancelCtx(parent Context) cancelCtx {
return cancelCtx{Context: parent}
}
Copy the code
We then call propagateCancel to terminate the child context when the parent context ends, and return func in addition to the cancelCtx reference. C.ancel () is called, where done() is called, C. canel ()
cancel
Cancel closes the cancelctx. done pipeline for the current and subcontexts.
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
// There must be a reason for closing
if err == nil {
panic("context: internal error: missing cancel error")
}
c.mu.Lock()
ifc.err ! =nil {
c.mu.Unlock()
return // Closed, return
}
c.err = err // Indicates that err is off
if c.done == nil {
c.done = closedchan
} else {
close(c.done) // Close the current done
}
// Since it is a map, the closing order is random
for child := range c.children {
child.cancel(false, err) // Iterate to cancel all subcontexts
}
c.children = nil // Remove the subcontext
c.mu.Unlock()
if removeFromParent {
removeChild(c.Context, c) // Delete yourself from the parent context}}Copy the code
propagateCancel
This function ensures that the child context ends when the parent context ends. On the one hand, the child is closed if the parent has been cancelled during the generation of the child context. On the other hand, if the parent context is kept open during execution, the child is also closed. Add the child to the children list of the parent, perform cancel, and then close.
func propagateCancel(parent Context, child canceler) {
done := parent.Done()
// If the parent's Done method returns null, the parent context is never cancelled
CTX, done := context.withcancel (context.background ())
if done == nil {
return
}
// If the parent context has been cancelled, close the current context
select {
case <-done:
child.cancel(false, parent.Err())
return
default:}// Father is not cancelled
if p, ok := parentCancelCtx(parent); ok {
p.mu.Lock()
// The father has cancelled and closed himself
ifp.err ! =nil {
child.cancel(false, p.err)
} else {
// Add child to parent's children
if p.children == nil {
p.children = make(map[canceler]struct{})
}
p.children[child] = struct{}{}
}
p.mu.Unlock()
} else {
// Parent context is a developer-defined type that enables a Goroutine to listen for parent and child contexts until one of them is closed
atomic.AddInt32(&goroutines, +1)
go func(a) {
select {
case <-parent.Done():
child.cancel(false, parent.Err())
case <-child.Done():
}
}()
}
}
Copy the code
WithTimeout and WithDeadline
type timerCtx struct {
cancelCtx
timer *time.Timer
deadline time.Time
}
func (c *timerCtx) Deadline(a) (deadline time.Time, ok bool) {
return c.deadline, true
}
Copy the code
TimerCtx is implemented by adding a timer and expiration time to cancelCtx.
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
// If the deadline passed in is later than the parent context's deadline, that means the parent context must end before the child
// It makes no sense to set a cut-off time for the subcontext in this case, so a cancelCtx is created directly
if cur, ok := parent.Deadline(); ok && cur.Before(d) {
return WithCancel(parent)
}
// Build a new timerCtx
c := &timerCtx{
cancelCtx: newCancelCtx(parent),
deadline: d,
}
// Ensure that the child is closed when the parent is closed
propagateCancel(parent, c)
// Calculate how long it is until d
dur := time.Until(d)
// Close the subcontext if the cutoff time has passed
if dur <= 0 {
c.cancel(true, DeadlineExceeded) // deadline has already passed
return c, func(a) { c.cancel(false, Canceled) }
}
c.mu.Lock()
defer c.mu.Unlock()
// c.err == nil indicates that the current context is not closed
if c.err == nil {
// AfterFunc waits for the duR to open a goroutine that executes the passed method, c. canel
// A timer is returned, which can be stopped and cancelled by calling the timer Stop method.
c.timer = time.AfterFunc(dur, func(a) {
c.cancel(true, DeadlineExceeded)
})
}
return c, func(a) { c.cancel(true, Canceled) }
}
Copy the code
The cancel method of timerCtx mainly calls cancelctx.cancel
func (c *timerCtx) cancel(removeFromParent bool, err error) {
Cancelctx. cancel to close the subcontext
c.cancelCtx.cancel(false, err)
// Remove the current context from the parent context
if removeFromParent {
removeChild(c.cancelCtx.Context, c)
}
c.mu.Lock()
ifc.timer ! =nil {
// Stop the timer and cancel the call
c.timer.Stop()
c.timer = nil
}
c.mu.Unlock()
}
Copy the code
WithTimeout calls WithDeadline directly
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
}
Copy the code
WithValue
func WithValue(parent Context, key, val interface{}) Context {
// Key cannot be nil
if key == nil {
panic("nil key")}// Key must be comparable
if! reflectlite.TypeOf(key).Comparable() {panic("key is not comparable")}return &valueCtx{parent, key, val}
}
type valueCtx struct {
Context
key, val interface{}}Copy the code
The provided key must be comparable and should not be of type string or any other built-in type to avoid collisions between packages using context. Users of WithValue should define their own types for keys.
Use custom struct{} as much as possible for key. Avoid using built-in data types to avoid conflicts when using context packages
conclusion
Context package is a model for concurrency control in complex scenarios added after Go 1.7. The core interface is context. context. This structure defines five methods to be implemented, which are used to send shutdown signals, set dateline, transfer values and other functions.
The core idea of the context package is to organize goroutine in a tree. When creating a new context, you need to specify a parent context. Therefore, the root context corresponds to the root Goroutine, and the sub-context corresponds to the sub-Goroutine, achieving flexible concurrency control.
RootContext is typically created from Background() or TODO(), which creates an empty emptyCtx, and then WithCancel() if you want to use the specific functionality of the context package, WithDateline() or WithValue() wraps the parent context into a concrete context object (cancelCtx, timerCtx, valueCtx), The first two methods return two values (CTX Context, done func()). Calling done sends a close signal to the Goroutine, which is obtained by monitoring ctx.done () in the Goroutine.
CancelCtx and timerCtx will remain the same children (timerCtx is actually a descendant of cancelCtx). This is a map. The key is canceler and the Value is struct{}. CancelCtx or timerCtx creates cancelCtx or timerCtx, which adds the current context to its parent’s children. When the parent closes, it traverses the children to close all the children and removes this context from its parent’s children. Due to the unordered nature of the map traversal, the order in which subcontexts are closed is also random.
The implementation of WithValue() and valueCtx is slightly different from the first two. On the one hand, valueCtx does not implement Done(), Deadline() and other methods by itself, so its function is limited to value transfer. There is no call to propagateCancel() in WithValue(), so valueCtx is not put in the children of the parent context, and valueCtx has no children of its own, so using valueCtx as the parent context makes no sense.
If not necessary, the WithValue() function is not usually used to pass the value, which is usually used to pass the authentication token of the user corresponding to the request or the request ID used for distributed tracing.