summary
The context package is golang1 6 start to provide the context package, golang1 7 move into the standard library. Context is only used in grpc, but it is not really used in its own program, and it does not understand its purpose. You must end with the context.
Main role
It mainly solves the problem that multiple goroutines and multiple links are nested and cannot be terminated (goroutine leakage problem) and the problem of context data sharing. In fact, it mainly solves the problem of goroutine termination, which is less used for general context data sharing.
realization
The implementation method is to close the transmission blocking of chancel through timeout, deadline, manual and other methods, complete the semaphore transmission, and realize goroutine termination. The source code implementation is that the context sends signals by manually or automatically calling the cancel method, and then the sub goroutine calls the Done method to receive signals. For the specific implementation, please refer to the context source code analysis
Context package source code analysis
common method
There are four main methods used
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
- If WithCancel sends the termination information manually, the CancelFunc method needs to be called manually
- WithDeadline sends in a precise deadline, such as (2021-8-2). When it reaches this time, the termination signal will be sent automatically
- WithTimeout passes in a countdown time, such as 100s. After 100s, the termination signal will be sent automatically
- WithValue passes in key and value values to facilitate context data sharing
Context interface
All the above four methods must be passed to context, which is a set of interfaces. Context defines a series of interfaces:
//Multiple goroutine s can call methods of the context at the same time. type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key interface{}) interface{} }
-
Deadline returns an end time point. ok indicates whether there is an end time point
-
Done Context terminates the signal, and its signal transmission method is implemented by chancel
-
Err returns the reason for the end of the Context. It will only be returned when Done returns
- when the manual termination is cancelled, the cancelled error is returned
- DeadlineExceeded error is returned when timeout termination is cancelled
- Value is only used when using the WithValue method. Generally, the superior passes in KV through the WithValue method, and the value is obtained through K.
Root Context
How to use the Context interface method? You need to initialize a root Context With, implement the Context interface internally through the four common With methods, and then pass the Context.
There are two implementations of root Context:
func Background() Context func TODO() Context
-
Background returns an empty Context, which is usually used for initialization in the main function and is generally used as a top-level Context. It has no expiration time, cannot be cancelled, and has no value
-
TODO also returns an empty Context, which is used when it is unclear which Context to use
The source code is:
type emptyCtx int .... var ( background = new(emptyCtx) todo = new(emptyCtx) ) func Background() Context { return background } func TODO() Context { return todo }
Both return an empty emptyCtx Context.
Four context s
There are four context s for different uses:
They are emptyCtx, cancelCtx, timerCtx and valueCtx
All four contexts implement Context interface, which is convenient for calling
emptyCtx
emptyCtx is an empty Context, and the return of the function is empty. It is mainly used on the Background
type emptyCtx int func (*emptyCtx) Deadline() (deadline time.Time, ok bool) { return } func (*emptyCtx) Done() <-chan struct{} { return nil } func (*emptyCtx) Err() error { return nil } func (*emptyCtx) Value(key interface{}) interface{} { return nil }
cancelCtx
CancelCtx is used in the WithCancel method. In the WithCancel method, a cancelCtx Context is created through newCancelCtx(Context), the main goroutine is then invoked by cancel() method of cancelCtx to send the stop signal, and the sub goroutine terminates the signal by means of the cancel() method of the cancelCtx.
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 } func (c *cancelCtx) Value(key interface{}) interface{} { if key == &cancelCtxKey { return c } return c.Context.Value(key) } func (c *cancelCtx) Done() <-chan struct{} { c.mu.Lock() if c.done == nil { c.done = make(chan struct{}) } d := c.done c.mu.Unlock() return d } func (c *cancelCtx) Err() error { c.mu.Lock() err := c.err c.mu.Unlock() return err } // cancel closes c.done, cancels each of c's children, and, if // removeFromParent is true, removes c from its parent's children. func (c *cancelCtx) cancel(removeFromParent bool, err error) { if err == nil { panic("context: internal error: missing cancel error") } c.mu.Lock() if c.err != nil { c.mu.Unlock() return // already canceled } c.err = err if c.done == nil { c.done = closedchan } else { close(c.done) } for child := range c.children { // NOTE: acquiring the child's lock while holding parent's lock. child.cancel(false, err) } c.children = nil c.mu.Unlock() if removeFromParent { removeChild(c.Context, c) } }
timerCtx
timerCtx is used on the WithDeadline and WithTimeout methods. The usage of these two methods is almost the same. They both send the termination signal automatically through the arrival of time. timerCtx Context is through timer time Afterfunc, you can view the WithDeadline source code
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) { if parent == nil { panic("cannot create context from nil parent") } 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, } propagateCancel(parent, c) dur := time.Until(d) if dur <= 0 { c.cancel(true, DeadlineExceeded) // deadline has already passed return c, func() { c.cancel(false, Canceled) } } c.mu.Lock() defer c.mu.Unlock() if c.err == nil { c.timer = time.AfterFunc(dur, func() { c.cancel(true, DeadlineExceeded) }) } return c, func() { c.cancel(true, Canceled) } }
type timerCtx struct { cancelCtx timer *time.Timer // Under cancelCtx.mu. deadline time.Time } func (c *timerCtx) Deadline() (deadline time.Time, ok bool) { return c.deadline, true } func (c *timerCtx) String() string { return contextName(c.cancelCtx.Context) + ".WithDeadline(" + c.deadline.String() + " [" + time.Until(c.deadline).String() + "])" } func (c *timerCtx) cancel(removeFromParent bool, err error) { c.cancelCtx.cancel(false, err) if removeFromParent { // Remove this timerCtx from its parent cancelCtx's children. removeChild(c.cancelCtx.Context, c) } c.mu.Lock() if c.timer != nil { c.timer.Stop() c.timer = nil } c.mu.Unlock() }
valueCtx
valueCtx is mainly used in the WithValue method, which is used for context data sharing.
The same Context is used for data sharing. In the case of the same key, the child Context will overwrite the value of the parent Context
type valueCtx struct { Context key, val interface{} } func (c *valueCtx) String() 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) }
Briefly summarize the four context s:
-
They all implement the Context interface
-
emptyCtx is an empty Context used to initialize the root Context
-
cancelCtx internally implements the Context of sending termination signals, mainly in the cancel method
-
timerCtx is the Context for sending due time and timeout, and inherits cancelCtx, which really calls the cancel method of cancelCtx
-
valueCtx is the Context used for Context data sharing
Sending and receiving of semaphores
Take WithCancel for example. The return of WithCancel is a Context interface and cancelfunction. Cancelfunction is a function type. Once the cancelfunction is called, it will send a termination signal.
The method corresponding to the cancelfunction is cancel()
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { if parent == nil { panic("cannot create context from nil parent") } c := newCancelCtx(parent) propagateCancel(parent, &c) return &c, func() { c.cancel(true, Canceled) } }
It mainly depends on these two interfaces. cancelCtx and timerCtx both implement these two methods
type canceler interface { cancel(removeFromParent bool, err error) //Send signal Done() <-chan struct{} //Receive signal read chan }
Let's mainly look at the implementation of the cancel method of cancelCtx:
func (c *cancelCtx) cancel(removeFromParent bool, err error) { if err == nil { panic("context: internal error: missing cancel error") } c.mu.Lock() if c.err != nil { c.mu.Unlock() return // already canceled } c.err = err if c.done == nil { c.done = closedchan //Send an empty struct to chan: var closedchan = make(chan struct {}) } else { close(c.done) } for child := range c.children { // NOTE: acquiring the child's lock while holding parent's lock. child.cancel(false, err) //Close sub Context } c.children = nil c.mu.Unlock() if removeFromParent { removeChild(c.Context, c) } }