php editor Zimo will introduce to you how to ensure that canceling the context will cause the goroutine to terminate. When we use goroutine, sometimes we need to cancel it when a certain condition is met to avoid unnecessary calculation and resource waste. To ensure that the goroutine terminates correctly, we can use the mechanism provided by the context package. The context package provides a way to pass requests between goroutines and cancel those requests when needed. By using the context package properly, we can ensure that the goroutine terminates correctly when the context is canceled, thereby avoiding resource leaks and other potential problems. Below we will detail how to use the context package to achieve this goal.
Assume the following situation occurs:
We have the following consumer
function, running in a goroutine.
Another goroutine is sending integers on the intchan
channel without delay. In other words, on each iteration of the for loop, there is a value ready to be received on intchan
.
Starting consumer
The goroutine for goroutine has canceled the context passed to consumer
. Therefore, the ctx.done()
channel also has a value to receive.
question:
select
will randomly select one case since both are ready to run. select
will not continue to select <- intchan
case? How do we know if <- ctx.done()
case is ready on every iteration of the for loop, how do we know <- ctx.done()
Will the case be finally selected? func consumer(ctx context.context, intchan chan int) { for { select { case <-ctx.done(): return case i := <-intchan: foo(i) } } }
I try to use the consumer
function in the program below.
In multiple runs of this program, the consumer
and producer
goroutines always seem to terminate.
Why don't we end up with a <-ctx.done()
run where the case is never executed?
package main import ( "context" "fmt" "sync" "time" ) func main() { ctx, cancelFunc := context.WithCancel(context.Background()) var wg sync.WaitGroup wg.Add(2) // add 2, because we spawn 2 goroutines Producer(ctx, &wg) fmt.Println(time.Now()) time.Sleep(time.Second * 5) // cancel the context after 5 seconds cancelFunc() fmt.Println("CANCELLED") wg.Wait() // wait till both producer and consumer goroutines terminate fmt.Println(time.Now()) } func Producer(ctx context.Context, wg *sync.WaitGroup) { intChan := make(chan int) go Consumer(ctx, intChan, wg) go func() { defer wg.Done() for { select { case <-ctx.Done(): return case intChan <- 1: } } }() } func Consumer(ctx context.Context, intChan chan int, wg *sync.WaitGroup) { defer wg.Done() for { select { case <-ctx.Done(): return case _ = <-intChan: } } }
There are no guarantees. The simplest way to guarantee termination is to check for errors using ctx.err()
outside the select statement. It's also common to return errors to code passing the context. I would write the consumer function like this:
func Consumer(ctx context.Context, intChan chan int) error { for ctx.Err() == nil { select { case <-ctx.Done(): case i := <-intChan: foo(i) } } return ctx.Err() }
The above is the detailed content of How do we guarantee that the canceled context will cause the goroutine to terminate?. For more information, please follow other related articles on the PHP Chinese website!