In concurrent programs, preemptive operations are often required because of timeouts, cancellations, or failures in other parts of the system. done channel can flow through your program and cancel all blocked concurrent operations.It looks good, but it's not perfect enough. What functions do you need?What function is perfect? These functions are useful if we can attach additional information to a simple notification; for example, why cancellations occur, and if there are deadlines (timeouts) for the function to complete.
The context package has the following methods: todo context (type type): there are: A Deadline function that indicates whether the goroutine is cancelled after a certain time. An Err method that returns non-zero if goroutine is cancelled. A Value method,
There are three cases of cancellation in a function: 1.The parent goroutine of the goroutine may want to cancel it. 2.A goroutine may want to cancel its subgoroutine 3.In goroutine, any blocking operation must be preemptive so that it can be cancelled.
Here's a look at the pros and cons of using do channel before
func printGreeting(done <-chan interface{}) error{ greeting, err := genGreeting(done) if err != nil{ return err } fmt.Printf("%s world!\n", greeting) return nil } func genGreeting(done <-chan interface{})(string, error){ switch locale, err := locale(done);{ case err != nil: return "", err case locale == "EN/US": return "hello", nil } return "", fmt.Errorf("unsupported locale") } func locale(done <-chan interface{})(string, error){ select{ case <-done : return "", fmt.Errorf("canceled") case <-time.After(1*time.Minute): } return "EN/US", nil } func printFarewell(done <-chan interface{}) error{ farewell, err := genFarewell(done) if err != nil{ return err } fmt.Printf("%s world!\n", farewell) return nil } func genFarewell(done <-chan interface{})(string, error){ switch locale,err := locale(done);{ case err != nil: return "", err case locale == "EN/US": return "goodbye", nil } return "", fmt.Errorf("unsupproted locale") } func main() { var wg sync.WaitGroup done := make(chan interface{}) defer close(done) wg.Add(1) go func() { defer wg.Done() if err := printGreeting(done); err != nil{ fmt.Println("%v", err) return } }() wg.Add(1) go func() { defer wg.Done() if err := printFarewell(done); err != nil{ fmt.Printf("%v", err) } }() wg.Wait() }
Suppose there is a need for: Assume genGreeting only wants to wait 1 s before abandoning the call to locale (it doesn't want to call more than 1 s) and timeout 1 s; If printGreeting is unsuccessful, we also want to cancel the call to printFarewall.After all, there is no point saying goodbye without saying hello.It's hard to do this with do channel, so let's see if context meets this need.
func main() { var wg sync.WaitGroup //The Background() method returns an empty context // withCancel wraps it for cancel/cancel methods/functions ctx, cancel := context.WithCancel(context.Background()) defer cancel() wg.Add(1) go func() { defer wg.Done(); if err := printGreeting(ctx); err != nil{ fmt.Printf("cannot print greeting: %v\n", err) //On this line, if a printout greeting error occurs, the context will be canceled; or cancel the context so that it can be used or derived from it. //In ct.Done(), a signal/value is received; thus, the blocking is unblocked. cancel() } }() wg.Add(1) go func() { defer wg.Done() if err := printFarewell(ctx); err != nil{ fmt.Printf("cannot print farewell: %v\n", err) } }() wg.Wait() } func printGreeting(ctx context.Context) error{ greeting, err := genGreeting(ctx) if err != nil{ return err } fmt.Printf("%s world!\n", greeting) return nil } func genGreeting(ctx context.Context)(string, error){ ctx, cancel := context.WithTimeout(ctx, 1*time.Second) defer cancel() switch locale, err := locale(ctx);{ case err != nil : return "", err case locale == "EN/US": return "hello", nil } return "", fmt.Errorf("unsupported locale") } func locale(ctx context.Context)(string, error){ select{ case <-ctx.Done(): return "", ctx.Err() case <-time.After(1* time.Minute) : } return "EN/US", nil } func printFarewell(ctx context.Context) error{ farewell, err := genFarewell(ctx) if err != nil{ return err } fmt.Printf("%s world!\n", farewell) return nil } func genFarewell(ctx context.Context)(string, error){ switch locale, err := locale(ctx);{ case err != nil: return "", err case locale == "EN/US": return "goodbye", nil } return "", fmt.Errorf("unsupported locale") } //cannot print greeting: context deadline exceeded //cannot print farewell: context canceled
Summary: cancel() or when it's time (context.WithTimeout(ctx, 1*time.Second)) will cause ctx.Done() to receive a value and unblock
So what is Deadline like?Or what are the requirements?
func locale(ctx context.Context)(string, error){ if deadline, ok := ctx.Deadline(); ok{ if deadline.Sub(time.Now().Add(1*time.Minute)) <= 0 { fmt.Println("end locale") return "", context.DeadlineExceeded } } select{ case <- ctx.Done(): return "", ctx.Err() case <-time.After(1*time.Minute): } return "EN/US", nil }
//end locale //cannot print greeting: context deadline exceeded //cannot print farewell: context canceled /** Here we check if our context provides a deadline.If it is provided, and can be determined that the current time for execution will certainly time out, the program will immediately return without going down the business logic; You can return a specific error in the context package, DeadlineExceeded This can save a lot of time in a program that calls the next expensive function, allowing it to fail immediately without waiting for the actual timeout to occur.Prerequisite/Applicable Conditions Must Know How long does a subordinate call graph take can be difficult to practice.
Figure: todo
In fact, the timeout setting must be greater than the normal program execution time; if a locale typically executes for 1 minute; then the timeout is 1 minute +; (In this case, to see The effect sets the timeout time to 1 s) The key to determining whether deadline satisfies <=0 is the now position (which is changing); what it actually means is that if the previous execution took too long (in case of an unexpected situation); then there is no need for the latter Execute again, because execution will definitely time out!
How to store and retrieve data in context:
func main(){ ProcessRequest("jane", "abc123") } func ProcessRequest(userID, authToken string){ ctx := context.WithValue(context.Background(), "userID", userID) ctx = context.WithValue(ctx, "authToken", authToken) HandleResponse(ctx) } func HandleResponse(ctx context.Context){ fmt.Printf("handling response for %v (%v)", ctx.Value("userID"), ctx.Value("authToken")) }
Storage uses context.WithValue(...); Retrieval uses ctx.Value() To use this function correctly, it is important to note that: /The conditions to be met are: 1.The keys used must satisfy comparable attributes/characteristics; that is, use operator==and!=Return correct results when using 2.Return values must be secure to be accessed from multiple goroutine s. How exactly?