当前位置:网站首页>Context of go concurrency mode

Context of go concurrency mode

2022-06-13 00:16:00 Ethan97

brief introduction

stay Go Language implementation of the server , Each incoming request is given a new go Process processing . The request processor often turns on additional go The coroutine accesses the backend , Like databases 、RPC service . These are created to serve the same request go A coroutine usually needs to access certain values of the request scope , For example, the identity of the end user 、 jurisdiction token、 Request deadline .
context Packages that allow the same request to be processed go It is more convenient to transfer values between processes . It can also process a set of requests go The cancellation signal is passed between processes 、 By the time deadline.

Context

Context The source of the interface is as follows :

// Context Methods of can be called concurrently 
type Context interface {
    

	// Deadline Return to the Context Time to be cancelled 
	Deadline() (deadline time.Time, ok bool)

	//  If it's time to Context Cancelled or timed out .Done A closed channel will be returned 
	Done() <-chan struct{
    }

	// Err Return to why Context Be cancelled 
	Err() error

	// Value Return and key Related key values 
	Value(key interface{
    }) interface{
    }
}

Why can you only check whether to cancel , And can not take the initiative to cancel :
One Context No, Cancel Method , also Done The channel returned by the method is one-way , receive calls only , Can't send . This is because A method of receiving a cancellation signal is usually not responsible for sending a cancellation signal . such as , A parent operation opens some go coroutines , these go A coroutine should not be able to cancel a parent go The work of the collaborative process .
One Context Can be multiple go Concurrent concurrent access of coroutines . You can put a Context To multiple go coroutines , Then by canceling the Context To cancel all go The work of the collaborative process .

derivative Context

context Packages provide methods from existing Context Derive new Context. these Context Form a tree . When one Context When cancelled , All the derivations from it Context All cancelled .
Background yes Context The root of a tree , It will never be cancelled . It is usually main function 、 Initialize or test using , And as the top level of the arrival request Context.
Background function :

// Background returns a non-nil, empty Context. It is never canceled, has no
// values, and has no deadline. It is typically used by the main function,
// initialization, and tests, and as the top-level Context for incoming
// requests.
func Background() Context {
    
	return background
}

WithCancel and WithTimeout The function uses the given Context Derive new Context. Usually when processing requests handler Cancel the... Related to the request after returning Context.WithCancel You can use multiple replica Cancel redundancy request . Besides ,WithTimeout You can set the deadline when requesting back-end services deadline.
WithCancel Source code is as follows :

// WithCancel Return to one parent And a new one Done passageway , If returned cancelFunc Called ,
//  Back to Context Of Done The passage will be closed , Or be a father Context Of Done The passage is closed , Back to Context Of Done The channel will also be closed .
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
    
	if parent == nil {
    
		panic("cannot create context from nil parent")
	}
	//  from parent Context Construct a Cancel Context
	c := newCancelCtx(parent)
	//  spread cancel, Be a father Context On cancellation , Son Context Will also cancel 
	propagateCancel(parent, &c)
	return &c, func() {
     c.cancel(true, Canceled) }
}

It calls for newCancelCtx

// newCancelCtx returns an initialized cancelCtx.
func newCancelCtx(parent Context) cancelCtx {
    
	return cancelCtx{
    Context: parent}
}

newCancelCtx Simple will Context Encapsulated in the CancelCtx.
cancelCtx Is defined as follows :

// A cancelCtx can be canceled. When canceled, it also cancels any children
// that implement canceler.
type cancelCtx struct {
    
	Context

	mu       sync.Mutex            // protects following fields
	done     atomic.Value          // of 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
}

Continue to look at WithCancel Another function called propagateCancel,propagateCancel It means to multiply 、 spread cancel. This function sets when the parent Context Cancel when cancelled Context. The source code is as follows :

// propagateCancel arranges for child to be canceled when parent is.
func propagateCancel(parent Context, child canceler) {
    
	// Done Will return a receive only channel , If done It's empty , Then father Context It will never be cancelled 
	done := parent.Done()
	if done == nil {
    
		return // parent is never canceled
	}

	select {
    
	case <-done:
		//  Father Context Has been cancelled , Jiangzi Context Also cancel 
		child.cancel(false, parent.Err())
		return
	default:
	}

	// parentCancelCtx return parent Corresponding cancelCtx
	if p, ok := parentCancelCtx(parent); ok {
    
		p.mu.Lock()
		if p.err != nil {
    
			//  Father Context Has been cancelled , Jiangzi Context Cancel 
			child.cancel(false, p.err)
		} else {
    
			//  Father Context Not cancelled , Initialize the parent Context Of children Domain , And add yourself to the parent CancelCtx Of children domain 
			if p.children == nil {
    
				p.children = make(map[canceler]struct{
    })
			}
			p.children[child] = struct{
    }{
    }
		}
		p.mu.Unlock()
	} else {
    
		// parentCancelCtx
		atomic.AddInt32(&goroutines, +1)
		//  Can't find parent Of CancelCtx, Then open a go The process monitors the parent Context, When it is found that it is cancelled, cancel the sub Context
		go func() {
    
			select {
    
			case <-parent.Done():
				child.cancel(false, parent.Err())
			case <-child.Done():
			}
		}()
	}
}

cancel The method comes from canceler Interface .canceler Can be used to cancel Context、 Check Context Is it cancelled :

// A canceler is a context type that can be canceled directly. The
// implementations are *cancelCtx and *timerCtx.
type canceler interface {
    
	cancel(removeFromParent bool, err error)
	Done() <-chan struct{
    }
}

cancelCtx The interface is implemented . comparison Context, It adds a mutex 、 A passage 、 A record Context Of children、 A record is cancelled err

// A cancelCtx can be canceled. When canceled, it also cancels any children
// that implement canceler.
type cancelCtx struct {
    
	Context

	mu       sync.Mutex            // protects following fields
	done     atomic.Value          // of 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
}

see cancel The logic of the method :

// cancel The method is closed cancelCtx The passage of , Cancel method receiver c The son of Context,
//  If removeFromParent Set to true, Will c From his father Context remove 
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
	//  From the above done The type of atomic.Value, The actual saved type is chan struct{},
	// Load Method will return the last Store Method to save the value , Otherwise it returns nil
	d, _ := c.done.Load().(chan struct{
    })
	if d == nil {
    
		// c.done No channels saved , Save a closed channel 
		c.done.Store(closedchan)
	} else {
    
		//  Close channel 
		close(d)
	}
	//  take children Domain saved child Context Cancel 
	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)
	}
}

go back to WithCancel Method , This method actually uses cancelCtx Encapsulates the incoming Context, The return type is still Context, And the encapsulated CancelCtx Of cancel Method returns , To master the parent Context Of go coroutines , Let it decide whether to cancel .

// WithCancel Returned a Context And cancel the Context Function of 
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
    
	if parent == nil {
    
		panic("cannot create context from nil parent")
	}
	//  from parent Context Construct a Cancel Context
	c := newCancelCtx(parent)
	//  Spread down cancel, Be a father Context On cancellation , Son Context Will also cancel 
	propagateCancel(parent, &c)
	return &c, func() {
     c.cancel(true, Canceled) }
}

Example

The following example is a server that processes requests . It's registered handleSearch To deal with it /search request . The processor creates an initial Context, And schedule it to cancel after the processor returns . If the request contains timeout Field ,Context stay timeout Automatically cancel after the time .

func handleSearch(w http.ResponseWriter, req *http.Request) {
    
    var (
        ctx    context.Context
        cancel context.CancelFunc
    )
    timeout, err := time.ParseDuration(req.FormValue("timeout"))
    //  Take root Context Call the above WithTimeout, Get son Context And cancellation methods cancel
    if err == nil {
    
        ctx, cancel = context.WithTimeout(context.Background(), timeout)
    } else {
    
        ctx, cancel = context.WithCancel(context.Background())
    }
    //  stay handleSearch The cancel method is called when the method returns , Will cancel the Context All the children under Context
    defer cancel()

Another example shows Context Cascade cancellation of :

func main() {
    
	route := gin.Default()

	route.GET("/hello", func(c *gin.Context) {
    
		// gin Default returned Context Namely context Under bag Background context
		ctx, cancel := context.WithCancel(c.Request.Context())
		//  Turn on additional go To process a request 
		go doSomething(ctx)
		time.Sleep(5 * time.Second)
		cancel()
	})
	
	route.Run(":8080")
}

// doSomething Will recursively create Context, But it does not turn off recursively created Context,
//  Father Context Cancel will cause all Context Cancel .
func doSomething(ctx context.Context) {
    
	for {
    
		time.Sleep(time.Second)
		select {
    
		case <-ctx.Done():
			fmt.Println("context is done.")
			return
		default:
			fmt.Println("context is not done...")
			//  No grandchildren Context Cancel , Dan sun Context Because of my father Context Cancel and cancel 
			c, _ := context.WithCancel(ctx)
			doSomething(c)
		}
	}
}

summary

context Package provides an interface for sharing values within the request domain , It also provides a set of open go The method of coordination process management , adopt Done Method can be easily implemented go Exit of coprocessor , adopt Deadline Ways to make go The coroutine determines whether to continue processing the request , adopt Err Method can record Context Reason for cancellation .

Reference material

Go Concurrency Patterns: Context

context documentation

原网站

版权声明
本文为[Ethan97]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/02/202202280602093274.html