当前位置:网站首页>Golang uses context gracefully

Golang uses context gracefully

2022-07-05 06:12:00 Dawnlighttt


Elegant closure goroutine


In many scenarios , When performing a task , We will split this task into several subtasks , Then open several different goroutine To carry out . When the task needs to be terminated for some reason , We need to put these goroutine And it's all over .

such as Go http Bag Server in , Each request has a corresponding goroutine To deal with . Request processing functions usually start additional goroutine Used to access back-end services , Such as databases and RPC service . Used to process a request goroutine You usually need to access some data specific to the request , For example, the identity authentication information of the end user 、 Verify the relevant token、 Deadline for requests . When a request is cancelled or timed out , All that is used to process the request goroutine You should quit quickly , Then the system can release these goroutine Occupied resources .

Let's take a simple example , Then use different methods to close the goroutine.


sync.WaitGroup Realization


package main

import (
	"fmt"
	"strconv"
	"sync"
	"time"
)

var wg sync.WaitGroup

func run(task string) {
    
	fmt.Println(task, "start...")
	time.Sleep(time.Second * 2)
	//  Every groutine Release after running WaitGroup The timer of 
	wg.Done()
}

func main() {
    
	wg.Add(2)
	for i := 1; i < 3; i++ {
    
		taskName := "task" + strconv.Itoa(i)
		go run(taskName)
	}
	wg.Wait()
	fmt.Println(" All the missions are over ")
}

// task2 start...
// task1 start...
//  All the missions are over 

In the example above , When a task is over, you must wait for another task to be over before it is all over , What is completed first must wait for other unfinished , be-all goroutine All must be completed before OK.

Advantages of this way : Use the concurrency control model of waiting group , It is especially suitable for many goroutine When working together to do one thing , Because of every goroutine It's all part of it , Only all goroutine It's all done , This thing is finished .

The drawbacks of this approach : In actual production , We need to take the initiative to inform someone goroutine end .


channel + select Realization


package main

import (
	"fmt"
	"time"
)

func main() {
    
	stop := make(chan bool)
	go func() {
    
		for {
    
			select {
    
			case <- stop:
				fmt.Println(" Mission 1  It's over ")
				return
			default:
				fmt.Println(" Mission 1  Running ")
				time.Sleep(time.Second)
			}
		}
	}()

	//  function 5 Seconds after stop 
	time.Sleep(time.Second * 5)
	stop <- true
	//  Stop testing goroutine Is it over 
	time.Sleep(time.Second * 3)
}

//  Mission 1  Running 
//  Mission 1  Running 
//  Mission 1  Running 
//  Mission 1  Running 
//  Mission 1  Running 
//  Mission 1  It's over 


Use channel + select The advantages of : More elegant .
Use channel + select The shortcomings of : If there are more than one goroutine What to do if it needs to be closed ? You can use the global bool Methods of type variables , But when assigning values to global variables, locks are needed to ensure the safety of the coroutine , This is bound to affect convenience and performance . What is more , If each goroutine Nested in goroutine Well ?


context Realization


Use the above code context rewrite :

package main

import (
	"context"
	"fmt"
	"time"
)

func main() {
    
	ctx, cancel := context.WithCancel(context.Background())
	//  Turn on goroutine, Pass in ctx
	go func(ctx context.Context) {
    
		for {
    
			select {
    
			case <- ctx.Done():
				fmt.Println(" Mission 1  It's over ")
				return
			default:
				fmt.Println(" Mission 1  Running ")
				time.Sleep(time.Second)
			}
		}
	}(ctx)

	//  Stop after five seconds 
	time.Sleep(time.Second * 5)
	cancel()
	//  Stop testing goroutine Is it over 
	time.Sleep(time.Second * 3)
}

//  Mission 1  Running 
//  Mission 1  Running 
//  Mission 1  Running 
//  Mission 1  Running 
//  Mission 1  Running 
//  Mission 1  It's over 


Use context Rewriting is relatively simple , Of course, the above is just a start goroutine The situation of , If there are more than one goroutine Well ?


package main

import (
	"context"
	"fmt"
	"time"
)

//  Use context Control multiple goroutine
func watch(ctx context.Context, name string) {
    
	for {
    
		select {
    
		case <- ctx.Done():
			fmt.Println(name, " sign out ,  Stopped. ")
			return
		default:
			fmt.Println(name, " Running ")
			time.Sleep(time.Second)
		}
	}
}

func main() {
    
	ctx, cancel := context.WithCancel(context.Background())
	go watch(ctx, " Mission 1")
	go watch(ctx, " Mission 2")
	go watch(ctx, " Mission 3")

	time.Sleep(time.Second * 3)
	//  Notify the task to stop 
	cancel()
	time.Sleep(time.Second * 2)
	fmt.Println(" Make sure all tasks stop ")
}

// Mission 3  Running 
// Mission 1  Running 
// Mission 2  Running 
// Mission 2  Running 
// Mission 3  Running 
// Mission 1  Running 
// Mission 1  Running 
// Mission 2  Running 
// Mission 3  Running 
// Mission 2  sign out ,  Stopped. 
// Mission 1  sign out ,  Stopped. 
// Mission 3  sign out ,  Stopped. 
// Make sure all tasks stop 

Above Context Like a controller , After pressing the switch , All based on this Context Or a derivative Context Will be notified , At this time, the cleaning operation can be carried out , Finally release goroutine, This is an elegant solution goroutine Uncontrollable problems after startup .


Gracefully close multiple goroutine nesting


package main

import (
	"context"
	"fmt"
	"time"
)

//  Define an inclusion context New type of 
type otherContext struct {
    
	context.Context
}

func work(ctx context.Context, name string)  {
    
	for {
    
		select {
    
		case <- ctx.Done():
			fmt.Println(name, " get msg to cancel")
			return
		default:
			fmt.Println(name, " is running")
			time.Sleep(time.Second)
		}
	}
}

func workWithValue(ctx context.Context, name string) {
    
	for {
    
		select {
    
		case <- ctx.Done():
			fmt.Println(name, " get msg to cancel")
			return
		default:
			value := ctx.Value("key").(string)
			fmt.Println(name, " is running value = ", value)
			time.Sleep(time.Second)
		}
	}
}

func main() {
    
	//  Use context.Background() Construct a WithCancel Type of context 
	ctxa, cancel := context.WithCancel(context.Background())

	// work Simulate the operation and detect the exit notification of the front end 
	go work(ctxa, "work1")

	//  Use WithDeadline Wrap the context object in front ctxa
	tm := time.Now().Add(3 * time.Second)
	ctxb, _ := context.WithDeadline(ctxa, tm)

	go work(ctxb, "work2")

	//  Use WithValue Wrap the context object in front ctxb
	oc := otherContext{
    ctxb}
	ctxc := context.WithValue(oc, "key", "andes, pass from main")

	go workWithValue(ctxc, "work3")

	//  deliberately  "sleep" 10 second ,  Give Way work2、work3 Timeout exit 
	time.Sleep(10 * time.Second)

	//  Display call work1 Of cancel Method to notify it to exit 
	cancel()

	//  wait for work1 Print exit message 
	time.Sleep(5 *time.Second)
	fmt.Println("main stop")
}

//work3 is running value = andes, pass from main
//work1 is running
//work2 is running
//work2 is running
//work3 is running value = andes, pass from main
//work1 is running
//work1 is running
//work3 is running value = andes, pass from main
//work2 is running
//work3 get msg to cancel
//work2 get msg to cancel
//work1 is running
//work1 is running
//work1 is running
//work1 is running
//work1 is running
//work1 is running
//work1 is running
//work1 get msg to cancel
//main stop


In the use of Context In the process of , The program actually maintains two relationship chains at the bottom .

  1. children key From root to leaf Context The reference relationship of the instance , Calling With Function time , Would call propagateCancel(parent Context, child canceler) Function for maintenance , The program has such a tree structure :

    ctxa.children --> ctxb
    ctxb.children --> ctxc
    

    This tree provides a way to traverse the tree from the root node ,context The packet cancellation notification is based on this tree , The cancellation notification is broadcast layer by layer from the root node to the lower node along this chain . Of course, you can also call cancel notification on any subtree , It will also spread to the whole tree .

  2. In the structure context Constantly wrapped in the object of context Instances form a chain of reference relationships , The direction of this relationship chain is the opposite , It's from the bottom up .

    ctxc.Context --> oc
    ctxc.Context.Context --> ctxb
    ctxc.Context.Context.cancelCtx --> ctxa
    ctxc.Context.Context.Context.cancelCtx.Context -> new(emptyCtx) // context.Background()
    

    This relationship chain is mainly used to cut off the current Context Instance and upper Context The relationship between instances ., such as ctxb Exit notification called or timer expired , ctxb There is no need to continue to exist on the notification broadcast tree , It needs to find its own parent , And then execute delete(parent.children,ctxb), Get rid of yourself from the radio tree .


net/http In bag context


net/http Package source code is being implemented http server When it comes to context, Let's briefly analyze :

1. First Server A... Is created when the service is opened valueCtx, Store server Information about , After that, each connection will start a process , And carry this valueCtx.

func (srv *Server) Serve(l net.Listener) error {
    

    ...

    var tempDelay time.Duration     // how long to sleep on accept failure
    baseCtx := context.Background() // base is always background, per Issue 16220
    ctx := context.WithValue(baseCtx, ServerContextKey, srv)
    for {
    
        rw, e := l.Accept()

        ...

        tempDelay = 0
        c := srv.newConn(rw)
        c.setState(c.rwc, StateNew) // before Serve can return
        go c.serve(ctx)
    }
}


2. The connection will be based on the incoming context Create a valueCtx Used to store local address information , Then on this basis, we created another cancelCtx, Then start reading network requests from the current connection , Every time a request is read, the cancelCtx Pass in , Used to transmit cancellation signals . Once the connection is broken , You can send a cancellation signal , Cancel all network requests in progress .

func (c *conn) serve(ctx context.Context) {
    
    c.remoteAddr = c.rwc.RemoteAddr().String()
    ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
    ...

    ctx, cancelCtx := context.WithCancel(ctx)
    c.cancelCtx = cancelCtx
    defer cancelCtx()

    ...

    for {
    
        w, err := c.readRequest(ctx)

        ...

        serverHandler{
    c.server}.ServeHTTP(w, w.req)

        ...
    }
}


3. After reading the request , Again, based on the incoming context Create a new cancelCtx, And set to the current request object req On , Generated at the same time response In the object cancelCtx Saved the current context Cancellation method .

func (c *conn) readRequest(ctx context.Context) (w *response, err error) {
    

    ...

    req, err := readRequest(c.bufr, keepHostHeader)

    ...

    ctx, cancelCtx := context.WithCancel(ctx)
    req.ctx = ctx

    ...

    w = &response{
    
        conn:          c,
        cancelCtx:     cancelCtx,
        req:           req,
        reqBody:       req.Body,
        handlerHeader: make(Header),
        contentLength: -1,
        closeNotifyCh: make(chan bool, 1),

        // We populate these ahead of time so we're not
        // reading from req.Header after their Handler starts
        // and maybe mutates it (Issue 14940)
        wants10KeepAlive: req.wantsHttp10KeepAlive(),
        wantsClose:       req.wantsClose(),
    }

    ...
    return w, nil
}


The main purposes of this treatment are as follows :

  • Once the request times out , To interrupt the current request ;
  • In the process of building response If something goes wrong in the process , Directly callable response Object's cancelCtx Method to end the current request ;
  • In the process of building response When it's done , call response Object's cancelCtx Method to end the current request .

Throughout server In the process , Used a context Chain penetration Server、Connection、Request, Not only will upstream information be shared with downstream tasks , At the same time, the upstream can send cancel signals to cancel all the downstream tasks , The cancellation of the downstream task will not affect the upstream task .


About Context To transfer data


First of all, we should clearly use context Package mainly solves goroutine Notification exit of , Transferring data is an additional function .
You can use it to pass some meta information , In short, use context The information transmitted cannot affect the normal business process , Don't expect the program to context Pass some necessary parameters in , Without these parameters , The program should also work properly .


context What data should be transferred

1. Log information .
2. Debugging information .
3. Optional data that does not affect the main business logic .


Summary


1.context It is mainly used for the synchronization cancellation signal between parent-child tasks , In essence, it is a way of scheduling . In addition, we are using context There are two points worth noting : Upstream tasks only use context Notify downstream tasks that no longer need , But it will not directly interfere with and interrupt the execution of downstream tasks , It's up to the downstream task to decide the subsequent processing operation , in other words context The cancellation of is non intrusive ;context It's thread safe , because context It is immutable in itself (immutable), Therefore, it can be safely used in multiple processes .

2.context The core functions provided by the package are multiple goroutine Exit notification mechanism between , Transferring data is only an auxiliary function , Use with caution context To transfer data .


Reference material :

《Go Language core programming 》
https://www.cnblogs.com/vinsent/p/11455531.html
https://zhuanlan.zhihu.com/p/110085652

原网站

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