How does a coroutine exit

When a coroutine is started, it usually exits automatically after the code is executed, but what if it needs to terminate early? One approach is to define a global variable that is checked for changes in the coroutine to determine whether to exit. This approach requires locking to ensure concurrency security. Speaking of which, is there any solution in mind? Select + channel to implement:

package main
import (
	"fmt"
	"sync"
	"time"
)
func main(a) {
	var wg sync.WaitGroup
	stopWk := make(chan bool)
	wg.Add(1)
	go func(a) {
		defer wg.Done()
		worker(stopWk)
	}()
	time.Sleep(3*time.Second) // Work for 3 seconds
	stopWk <- true // The stop command is issued after 3 seconds
	wg.Wait()
}

func worker(stopWk chan bool){
	for {
		select {
		case <- stopWk:
			fmt.Println("Time off...")
			return
		default:
			fmt.Println("Carefully touch the fish, do not disturb...")
		}
		time.Sleep(1*time.Second)
	}
}
Copy the code

Running result:

Touch the fish carefully, do not disturb... Touch the fish carefully, do not disturb... Touch the fish carefully, do not disturb... Go off work to cough up ~ ~ ~Copy the code

As you can see, “Carefully touch fish, do not disturb…” is printed once a second. , 3 seconds after the stop instruction, the program into “off duty cough ~~~”.

At the beginning of the Context experience

Above we used select+channel to terminate coroutines, but what if we want to cancel multiple coroutines at once? What if you need to cancel regularly? This is where the Context comes in. It can track each coroutine. Let’s rewrite the example above:

package main
import (
	"context"
	"fmt"
	"sync"
	"time"
)
func main(a) {
	var wg sync.WaitGroup
	ctx, stop := context.WithCancel(context.Background())
	wg.Add(1)
	go func(a) {
		defer wg.Done()
		worker(ctx)
	}()
	time.Sleep(3*time.Second) // Work for 3 seconds
	stop() // The stop command is issued after 3 seconds
	wg.Wait()
}

func worker(ctx context.Context){
	for {
		select {
		case <- ctx.Done():
			fmt.Println("Time off...")
			return
		default:
			fmt.Println("Carefully touch the fish, do not disturb...")
		}
		time.Sleep(1*time.Second)
	}
}
Copy the code

Running result:

Touch the fish carefully, do not disturb... Touch the fish carefully, do not disturb... Touch the fish carefully, do not disturb... Go off work to cough up ~ ~ ~Copy the code

The Context is introduced

Context is concurrency safe. It is an interface that can issue cancellation signals and transmit values manually, regularly, or timeout. Context is mainly used to control cooperation and cancellation operations among multiple coroutines.

The Context interface has four methods:

type Context interface {
   Deadline() (deadline time.Time, ok bool)
   Done() <-chan struct{}
   Err() error
   Value(key interface{}) interface{}}Copy the code
  • Deadline method: You can get the set Deadline. The return value “Deadline” is the set Deadline. At this time, the Context automatically initiates a cancellation request.
  • The Done method returns a read-only channel of type struct{}. If the chan can be read, the cancellation signal has been issued and you can clean up and exit the coroutine, freeing the resource.
  • The Err method: returns the reason why the Context was cancelled.
  • Value method: Obtain the Value bound to the Context. It is a key-value pair. Obtain the corresponding Value by key.

The most common method is the Done method, which closes the read-only Channel when the Context is cancelled, signaling cancellation.

The Context tree

We don’t need to implement the Context interface ourselves. The Go language provides functions to generate different contexts. These functions can generate a Context tree so that the Context can be associated, and the parent Context can cancel. Sub-context is also emitted, so you can control coroutine exit at different levels.

Generating a root node

  1. emptyCtxIt’s an int variable, but it implements the context interface.emptyCtxThere’s no timeout, no cancellation, and no extra information to store, soemptyCtxIt’s used as the root of the context tree.
  2. But we don’t usually use it directlyemptyCtx, but is used byemptyCtxInstantiate two variables (Background, todo), respectively by callingBackgroundandTODOMethod, but these two contexts are implemented identically.

Background and TODO methods: Background and TODO are only used in different scenarios :Background is usually used in main functions, initializations, and tests, as a top-level context, which means that we usually create context based on Background; TODO is used when you’re not sure what context to use.

Spanning tree function

  1. You can do that by context. Background() gets a root Context.
  2. Once you have the root node, use the following four functions to generate the Context tree:
  • WithCancel(parent Context) : Generates a cancerable Context.
  • WithDeadline(parent Context, d time.time) : generates a Context that can be cancelled periodically. Parameter D is the specific time for cancellation.
  • WithTimeout(parent Context, timeout time.duration) : Generates a Context that can be cancelled out of time. The timeout parameter is used to set how long to cancel
  • WithValue(parent Context, key, val interface{}) : Generates a Context that can carry key-value pairs.

Context cancels multiple coroutines

If a Context has a subcontext, when that Context is cancelled, all subcontexts under it are cancelled.

The Context by value

Context can not only signal cancellation, it can also pass values, and it can make the values it stores available to other coroutines.

Example:

package main
import (
	"context"
	"fmt"
	"sync"
	"time"
)
func main(a) {
	var wg sync.WaitGroup
	ctx, stop := context.WithCancel(context.Background())
	valCtx := context.WithValue(ctx, "position"."gopher")
	wg.Add(2)
	go func(a) {
		defer wg.Done()
		worker(valCtx, "Worker 1")
	}()
	go func(a) {
		defer wg.Done()
		worker(valCtx, "Worker 2")
	}()
	time.Sleep(3*time.Second) // Work for 3 seconds
	stop() // The stop command is issued after 3 seconds
	wg.Wait()
}

func worker(valCtx context.Context, name string){
	for {
		select {
		case <- valCtx.Done():
			fmt.Println("Time off...")
			return
		default:
			position := valCtx.Value("position")
			fmt.Println(name,position, "Carefully touch the fish, do not disturb...")
		}
		time.Sleep(1*time.Second)
	}
}
Copy the code

Running result:

Working people2Please don't disturb the fish... Working people1Please don't disturb the fish... Working people1Please don't disturb the fish... Working people2Please don't disturb the fish... Working people2Please don't disturb the fish... Working people1Please don't disturb the fish... Off duty, off duty, off dutyCopy the code

Context Usage Principles

  • You don’t want to put a Context in a structure, you need to pass it as a parameter
  • Context is the first parameter to a function
  • Use the context. The Background function generates the Context of the root node
  • You want to pass the value of the Context the necessary value, not everything, okay
  • Context is multicoroutine safe and can be used in multiple coroutines