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 functionscontext.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
  • 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:

  1. Background: is the context default from which all other contexts should derive
  2. 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:

  1. 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…