This is a creation in Article, where the information may have evolved or changed.
In the previous article, Golang context, the use of the context and the application scenarios have been preliminarily understood. Then go deep into the source to learn how the context is implemented.
Emptyctx
The context package code is very small, a context.go file, a total of 480 lines of code, which also includes a large number of comments. The context package first defines a context interface:
type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key interface{}) interface{}}
Next, you define a emptyCtx
type:
// An emptyCtx is never canceled, has no values, and has no deadline. It is not// struct{}, since vars of this type must have distinct addresses.type emptyCtx int
Why is emptyCtx
it called? The note says it emptyCtx
cannot be canceled, has no value, and has no deadline. At the same time, emptyCtx
it is not one struct{}
, because this type of variable needs to have a different address.
This emptyCtx
implements the Context
interface:
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}func (e *emptyCtx) String() string { switch e { case background: return "context.Background" case todo: return "context.TODO" } return "unknown empty Context"}
Read the above code to know why emptyCtx
can not be canceled, no value, no deadline, because the above implementation is directly return. So, emptyCtx
What's the use of this? Do you remember Background()
the TODO()
function? Yes, their interior is a pointer to the direct return emptyCtx
type:
var ( background = new(emptyCtx) todo = new(emptyCtx))func Background() Context { return background}func TODO() Context { return todo}
So these two functions are generally used in the main
context of functions, initialization, testing, and top-level requests. OK, keep looking down.
Since the emptyCtx
type does nothing, there should be other types to implement the relevant functions, that is, cancelCtx
timerCtx
, valueCtx
three types. Here's a look at these three types.
Cancel
As we know in the previous article, WithCancel
WithTimeout
WithDeadline
These three methods return a function of a CancelFunc
type that defines the interface within the context canceler
:
type canceler interface { cancel(removeFromParent bool, err error) Done() <-chan struct{}}
canceler
is a type of context that can be canceled directly, and will continue to look down we will find that, and not only cancelCtx
timerCtx
implemented the context interface (through anonymous member variables), but also implemented the canceler
interface.
Cancelctx
cancelCtx
Definition of the struct body:
type cancelCtx struct { Context done chan struct{} // closed by the first cancel call. mu sync.Mutex children map[canceler]struct{} // set to nil by the first cancel call err error // set to non-nil by the first cancel call}
Method Set:
Func (c *cancelctx) done () <-chan struct{} {return C.done}func (c *cancelctx) ERR () error {C.mu.lock () defer C.mu.unlock () return C.err}func (c *cancelctx) string () string {return FMT. Sprintf ("%v.withcancel", C.context)}//cancel closes C.done, cancels each of the C ' s children, and, if//removefromparent is t Rue, 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.unloc K () return//already canceled} C.err = err//Turn off C's done channel, all monitor C. The goroutine of Done () will receive the message close (C.done)//cancels the child, because it is a map structure, so the order of cancellation is not fixed for the child: = Range C.children {//N Ote:acquiring the child's lock while holding parent ' s lock. Child.cancel (False, err)} C.children = nil C.mu.unlock ()//Remove removed child if removefromparent from C.children { RemoveChild (C.context,c)}}
When we call WithCancel
, we actually return a cancelCtx
pointer and a cancel()
method:
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { c := newCancelCtx(parent) propagateCancel(parent, &c) return &c, func() { c.cancel(true, Canceled) }}// newCancelCtx returns an initialized cancelCtx.func newCancelCtx(parent Context) cancelCtx { return cancelCtx{ Context: parent, done: make(chan struct{}), }}
So what propagateCancel
does a function do?
Find the nearest parent context that can be canceled, and put the child context into the parent. Children in//Propagatecancel arranges for child to being canceled when parent Is.func Propagatecancel (parent Context, child CA Nceler) {if parent. Done () = = Nil {return//parent is never canceled}//Determines whether the returned parent is cancelctx if p, OK: = PARENTCANCELCT X (parent); OK {p.mu.lock () if p.err! = Nil {//parent has already been canceled Child.cancel (FA LSE, P.ERR)} else {if P.children = = Nil {P.children = make (map[canceler]struct{}) } P.children[child] = struct{}{}} p.mu.unlock ()} else {go func () { Select {Case <-parent. Done (): Child.cancel (False, parent.) ERR ()) Case <-child. Done ():}} ()}}//Parentcancelctx follows a chain of parent references until it finds a//*cancelctx . This function understands what each of the concrete types in this//The package represents its parent.//keeps looking up for the most recently canceled parent Contextfunc Parentcancelctx (*CANCELCTX, bool) {for { Switch c: = parent. (type) {case *cancelctx:return C, true case *timerctx:return &c.cancelctx, True Case *valuectx:parent = C.context Default:return nil, False}}}
Timer
WithTimeout
And WithDeadline
actually is similar, but the source code inside to help us encapsulate a bit:
Func withtimeout (parent Context, timeout time. Duration) (Context, Cancelfunc) {return Withdeadline (parent, time. Now (). ADD (timeout))}func withdeadline (parent Context, Deadline time. Time) (Context, Cancelfunc) {//Current deadline is earlier than the new deadline, return directly if cur, OK: = parent. Deadline (); OK && cur. Before (deadline) {//The current deadline is already sooner than the new one. return Withcancel (Parent)} c: = &timerctx{cancelctx:newcancelctx (parent), Deadline:deadline, } propagatecancel (parent, c) d: = time. Until (Deadline)///deadline is gone, no longer set timer if D <= 0 {C.cancel (true, deadlineexceeded)//deadline has Alrea Dy passed return C, func () {C.cancel (True, Canceled)}} c.mu.lock () defer c.mu.unlock () if c.err = = N Il {//Set D time after execution cancellation method C.timer = times. Afterfunc (D, func () {C.cancel (True, Deadlineexceeded)})} return C, func () {C.cancel (True, Canc eled)}}
timerCtx
The code is also relatively simple to implement:
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 fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, time.Until(c.deadline))}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()}
It is important to note that timerCtx
there is no direct implementation of the canceler
interface, but the use of anonymous member variables, so that you do not have to implement the context interface again, but only to implement the method on demand Deadline
. Method is timerCtx
cancel
called First cancelCtx
cancel
, and then the timer is stopped.
Value
First look at valueCtx
the definition:
type valueCtx struct { Context key, val interface{}}func (c *valueCtx) String() string { return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val)}func (c *valueCtx) Value(key interface{}) interface{} { // 找到了直接返回 if c.key == key { return c.val } // 向上继续查找 return c.Context.Value(key)}
This is the simplest context implementation, in addition to the anonymous variable context, there are two additional Key,value variables to store the value. Look at WithValue
the implementation:
func WithValue(parent Context, key, val interface{}) Context { if key == nil { panic("nil key") } if !reflect.TypeOf(key).Comparable() { panic("key is not comparable") } return &valueCtx{parent, key, val}}
It is important to note that key cannot be nil and must be comparable, otherwise it will cause panic! Can be compared to the meaning of the key can not be a function type or Nan, or the like, specifically can look at the reflect package, here is not elaborate.
At last
The whole context look down, to the context is how to achieve also have a clear understanding, and context package has a lot of test code, very good! Recently looking at the source of go, found that really is a good learning material, how to write concise and clear code is very helpful.